openhound-github 0.22.0__tar.gz → 0.22.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openhound_github-0.22.0 → openhound_github-0.22.1}/PKG-INFO +22 -1
- {openhound_github-0.22.0 → openhound_github-0.22.1}/README.md +21 -0
- openhound_github-0.22.1/src/openhound_github/github_rest_client.py +90 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/helpers.py +15 -8
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/resources/organization.py +28 -5
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/source.py +2 -5
- openhound_github-0.22.1/tests/test_collection_continuation.py +153 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_github_app_retry.py +36 -0
- openhound_github-0.22.1/tests/test_github_rest_client.py +161 -0
- openhound_github-0.22.1/tests/test_graphql_retry_budget.py +100 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_workflow_resources.py +61 -2
- openhound_github-0.22.0/src/openhound_github/github_rest_client.py +0 -3
- openhound_github-0.22.0/tests/test_github_rest_client.py +0 -52
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.dockerignore +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/CONTRIBUTING.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/GOVERNANCE.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/SECURITY.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/SO-Dark.svg +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/SO-FOSS-Dark.svg +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/SO-FOSS-Light.svg +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/SO-Light.svg +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/workflows/publish-container.yml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/workflows/publish.yml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/workflows/release-on-merge.yml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.github/workflows/validate-branch.yml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.gitignore +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/.pre-commit-config.yaml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/Dockerfile +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/LICENSE.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AddAssignee.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AddCollaborator.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AddLabel.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AddMember.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AddedDeployKey.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AdminTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ApprovesDeploymentTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AssignedTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_AuthorizedForOrganization.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_BypassBranchProtection.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_BypassPullRequestAllowances.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CallsWorkflow.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanAccess.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanAccessSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanAssumeIdentity.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreateBranch.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreateEnvironment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreateInternalRepositories.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreatePrivateRepositories.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreatePublicRepositories.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreateRepositories.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanCreateRepositoryWithRunnerAccess.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanDeployToEnvironment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanEditEnvironment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanEditProtection.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanInterceptJob.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanPwnRequest.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanReadSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanReadSecretScanningAlert.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanRequestOIDCTokenFor.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanUseRunner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CanWriteBranch.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CloseDiscussion.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CloseIssue.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ClosePullRequest.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_Contains.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ConvertIssuesToDiscussions.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateDiscussionCategory.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateEnterpriseOrganizations.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateRepository.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateSoloMergeQueueEntry.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateTag.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_CreateTeam.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeleteAlertsCodeScanning.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeleteDiscussion.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeleteDiscussionComment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeleteIssue.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeleteTag.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DependsOn.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_DeploysTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditCategoryOnDiscussion.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditDiscussionCategory.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditDiscussionComment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditEnterpriseCustomPropertiesForOrganizations.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditRepoAnnouncementBanners.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditRepoCustomPropertiesValues.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditRepoMetadata.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_EditRepoProtections.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasBaseRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasExternalIdentity.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasMember.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasPersonalAccessToken.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasPersonalAccessTokenRequest.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasRunner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasSamlIdentityProvider.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_HasVariable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_InheritedFrom.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_InstalledAs.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_InviteMember.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_IsEligibleFor.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_JumpMergeQueue.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageDeployKeys.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageDiscussionBadges.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseAdmins.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseIdentityProvider.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseMembers.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseOrganizationAdmins.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseOrganizations.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseReferrals.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageEnterpriseTeams.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageOrganizationWebhooks.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageRepoSecurityProducts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageSecurityProducts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageSettingsMergeTypes.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageSettingsPages.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageSettingsProjects.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageSettingsWiki.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageTopics.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ManageWebhooks.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_MapsToUser.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_MarkAsDuplicate.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_MatchesEnvironmentPolicy.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_MemberOf.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_OrgBypassCodeScanningDismissalRequests.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_OrgBypassSecretScanningClosureRequests.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_OrgReviewAndManageSecretScanningBypassRequests.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_OrgReviewAndManageSecretScanningClosureRequests.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_Owns.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ProtectedBy.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_PushProtectedBranch.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadCodeScanning.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadEnterpriseAuditLog.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadEnterpriseDomainVerification.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadEnterpriseMembers.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadEnterpriseOrgProjects.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadEnterpriseOrganizationAdmin.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadOrganizationActionsUsageMetrics.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadOrganizationCustomOrgRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadOrganizationCustomRepoRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReadRepoContents.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RemoveAssignee.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RemoveLabel.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReopenDiscussion.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReopenIssue.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ReopenPullRequest.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RequestPrReview.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RequestsAccessTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ResolveDependabotAlerts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ResolveSecretScanningAlerts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RestrictionsCanPush.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RunOrgMigration.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_RunsOn.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ScopedTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SetEnterpriseInteractionLimits.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SetInteractionLimits.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SetIssueType.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SetMilestone.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SetSocialPreview.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_SyncedTo.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ToggleDiscussionAnswer.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ToggleDiscussionCommentMinimize.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_TransferRepository.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_UsesSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_UsesVariable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ValidToken.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ViewDependabotAlerts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ViewEnterpriseActionsUsageMetrics.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ViewEnterpriseBilling.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ViewEnterpriseSecretScanningAlerts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_ViewSecretScanningAlerts.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteCodeScanning.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteEnterpriseActionsPolicies.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteEnterpriseBilling.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteEnterprisePersonalAccessTokenPolicies.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteEnterpriseSso.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteEnterpriseTeamMembers.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationActionsSecrets.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationActionsSettings.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationActionsVariables.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationCustomOrgRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationCustomRepoRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteOrganizationNetworkConfigurations.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteRepoContents.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/edges/GH_WriteRepoPullRequests.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_App.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_AppInstallation.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Branch.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_BranchProtectionRule.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_ClassicPersonalAccessToken.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_DeployKey.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Enterprise.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnterpriseManagedUser.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnterpriseRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnterpriseRunner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnterpriseRunnerGroup.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnterpriseTeam.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Environment.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnvironmentBranchPolicy.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnvironmentSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_EnvironmentVariable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_ExternalIdentity.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_OrgRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_OrgRunner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_OrgRunnerGroup.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_OrgSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_OrgVariable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Organization.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_PersonalAccessToken.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_PersonalAccessTokenRequest.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_RepoRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_RepoRunner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_RepoSecret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_RepoVariable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Repository.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Runner.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_RunnerGroup.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_SamlIdentityProvider.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Scope.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Secret.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_SecretScanningAlert.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Team.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_TeamRole.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_User.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Variable.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_Workflow.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_WorkflowJob.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/descriptions/nodes/GH_WorkflowStep.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/.dlt/config.toml.example +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/.dlt/secrets.toml.example +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/.env.example +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/README.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/check_config.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/docker-compose/compose.yaml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-all-repo-admin-role.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-app-installations-all-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-apps-all-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-enterprise-owners-role.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-external-identities-owners.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-organizations.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-owner-users.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-owners-role.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-pats-all-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-privilege-escalation-roles.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-privilege-escalation-users.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/privilege_zone_rules/t0-saml-identity-providers.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/README.md +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/actions-sha-pinning-not-required.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/active-leaked-secrets.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/advanced-security-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/all-actions-allowed.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/app-installations-all-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-admins-not-enforced.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-deletions-allowed.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-force-pushes.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-no-code-owner-reviews.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-no-pr-reviews.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-no-status-checks.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-self-approval.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/branch-protection-stale-reviews.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/bypass-pr-requirements.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/dangerous-branch-perms.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/default-repository-permissions.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/demo-sso-to-cloud-round-trip.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/dependabot-alerts-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/dependabot-updates-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/dependency-graph-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/environments-admin-bypass.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/expired-pats.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/external-identities-without-scim.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/github-to-azure-identity.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/global-repo-perms.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/hybrid-identities.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-change-repo-visibility.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-create-pages.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-create-public-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-delete-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-fork-private-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/members-can-invite-outside-collaborators.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/org-owners.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/orgs-without-2fa.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/pats-all-repo-access.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/pending-pat-requests.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/private-repos-forking-allowed.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/privileged-custom-org-roles.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/privileged-hybrid-identities.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/public-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/push-protection-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/push-to-protected-branches.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/repos-secret-scanning-disabled.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/repos-vulnerable-to-workflow-secret-exfil.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/repository-workflows.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/saml-configuration.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/secret-scanning-alerts.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/secret-scanning-disabled-new-repos.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/secrets-reachable-by-user.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/self-hosted-runners-can-intercept-broad-token-jobs.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/shared-self-hosted-runners-can-intercept-secret-bearing-jobs.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/team-membership-admin.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/team-structure.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/unprotected-branches.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/unprotected-default-branch-with-workflow.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/unprotected-default-branches.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/web-commit-signoff-not-required.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/workflow-jobs-interceptable-by-self-hosted-runners.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/workflow-jobs-with-broad-token-write-permissions.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/workflow-jobs-with-id-token-write-on-self-hosted-runners.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/workflow-jobs-with-id-token-write.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/saved_searches/workflow-jobs-with-observed-oidc-auth-steps.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/extension/schema.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/justfile +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/pyproject.toml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/scripts/generate_gh_description_docs.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/main.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/__init__.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/auth.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/extension.yaml +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/github_retry.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/graph.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/graphql.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/kinds/edges.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/kinds/nodes.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/lookup.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/main.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/__init__.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/actions_permission.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/app_installation.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/branch.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/branch_pr_bypass_allowance.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/branch_protection_rule.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/branch_push_allowance.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/classic_personal_access_token.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/deploy_key.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_admin.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_credential_inventory.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_helpers.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_managed_user.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_member.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_organization.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_role.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_role_team.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_role_user.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_team.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_team_member.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_team_organization.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_team_role.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/enterprise_user.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/env_secret.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/env_variable.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/environment.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/environment_branch_policy.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/external_identity.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org_role.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org_role_member.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org_role_team.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org_secret.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/org_variable.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/permissions.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/personal_access_token.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/personal_access_token_access.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/personal_access_token_request.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/projected_enterprise_team.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/repo_role_assignment.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/repository.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/repository_role.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/repository_secret.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/repository_variable.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/runner.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/saml_assertion_consumer_service.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/saml_helpers.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/saml_issuer.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/saml_provider.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/saml_service_provider.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/scim_user.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/scope.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/secret_scanning_alert.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/team.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/team_member.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/team_role.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/user.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/workflow.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/workflow_job.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/workflow_reference.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/models/workflow_step.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/resources/__init__.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/resources/enterprise.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/runner_ids.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/transforms.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/src/scheduler.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_app_auth.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_app_installation_repo_access.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_app_model.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_branch_collection_gate.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_branch_protection_traversability.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_classic_personal_access_tokens.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_credential_permission_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/actions-permissions.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/actionsecrets.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/app.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/appinstalls.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/branch-policies.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/branch-prot-rules.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/branches.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/custom-roles.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/environments.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org-pat-request.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org-pat.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org-role-teams.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org-role-users.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org-roles.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/org.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/pat-repositories.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/repo-collaborators.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/repo-teams.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/repositories.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/scimusers.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/secrets.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/secretscanningalert.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/selected-org-secrets.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/variables.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_data/api/workflows.json +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_deploy_key_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_deploy_key_resources.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_description_docs.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_enterprise_capabilities.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_enterprise_organization.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_enterprise_resources.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_environment_model.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_environment_resources.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_environment_reviewer_lookup.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_error_resilience.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_github_retry.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_helpers.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_lookup.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_org_role_model.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_org_scim_resources.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_org_secret_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_preproc.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_repository_rulesets.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_repository_scopes.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_runner_ids.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_runner_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_runner_resources.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_saml_helpers.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_saml_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_scim_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_source_endpoints.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_source_org_canonicalization.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_team_external_groups.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_variable_models.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_workflow_interception_path.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/tests/test_workflow_model.py +0 -0
- {openhound_github-0.22.0 → openhound_github-0.22.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: openhound-github
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.1
|
|
4
4
|
Summary: Github collector for OpenHound
|
|
5
5
|
Author-email: "jdreijer@specterops.io" <jdreijer@specterops.io>, Jared Atkinson <jatkinson@specterops.io>
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -66,6 +66,27 @@ Both values must be provided together when overriding the GitHub.com defaults.
|
|
|
66
66
|
Endpoint URLs must use HTTPS and share the same origin. GitHub App
|
|
67
67
|
`credentials.api_uri` may override only the path on that same origin.
|
|
68
68
|
|
|
69
|
+
### Request timeouts and retries
|
|
70
|
+
|
|
71
|
+
REST and GraphQL collection requests use a 10-second connection timeout and a
|
|
72
|
+
30-second read timeout. Transient HTTP 5xx errors, connection failures, and
|
|
73
|
+
timeouts share a limit of three attempts (the initial attempt plus two retries).
|
|
74
|
+
Adaptive GraphQL pagination shares that limit across page sizes: HTTP 502/504
|
|
75
|
+
responses trigger smaller-page attempts instead of a new retry budget.
|
|
76
|
+
|
|
77
|
+
After retries are exhausted, existing resource handlers log the failure and
|
|
78
|
+
continue with unaffected scopes. Already emitted records are retained, but the
|
|
79
|
+
affected scope may be incomplete. Resources that require all pages before
|
|
80
|
+
emitting records retain that behavior.
|
|
81
|
+
|
|
82
|
+
Workflow collection skips `dynamic/` paths. Missing workflow files are logged
|
|
83
|
+
and skipped without passing failed items to job or step parsing.
|
|
84
|
+
|
|
85
|
+
Rate-limit responses and GitHub App token refresh retain their existing retry
|
|
86
|
+
handling and do not consume the transient-failure budget. These timeouts are
|
|
87
|
+
not an overall request deadline. App authentication requests and credential
|
|
88
|
+
inventory CSV downloads retain their separate timeout settings.
|
|
89
|
+
|
|
69
90
|
### Enterprise SCIM and hybrid correlations
|
|
70
91
|
|
|
71
92
|
A token with enterprise SCIM access is used to collect both `/scim/v2/enterprises/{enterprise}/Users` and `/scim/v2/enterprises/{enterprise}/Groups`. The collector emits normalized `SCIM_Organization`, `SCIM_User`, and `SCIM_Group` nodes plus `SCIM_Contains`, `SCIM_MemberOf`, and `SCIM_Provisioned` relationships. Install the BloodHound SCIM extension alongside this extension to register the shared SCIM kinds.
|
|
@@ -55,6 +55,27 @@ Both values must be provided together when overriding the GitHub.com defaults.
|
|
|
55
55
|
Endpoint URLs must use HTTPS and share the same origin. GitHub App
|
|
56
56
|
`credentials.api_uri` may override only the path on that same origin.
|
|
57
57
|
|
|
58
|
+
### Request timeouts and retries
|
|
59
|
+
|
|
60
|
+
REST and GraphQL collection requests use a 10-second connection timeout and a
|
|
61
|
+
30-second read timeout. Transient HTTP 5xx errors, connection failures, and
|
|
62
|
+
timeouts share a limit of three attempts (the initial attempt plus two retries).
|
|
63
|
+
Adaptive GraphQL pagination shares that limit across page sizes: HTTP 502/504
|
|
64
|
+
responses trigger smaller-page attempts instead of a new retry budget.
|
|
65
|
+
|
|
66
|
+
After retries are exhausted, existing resource handlers log the failure and
|
|
67
|
+
continue with unaffected scopes. Already emitted records are retained, but the
|
|
68
|
+
affected scope may be incomplete. Resources that require all pages before
|
|
69
|
+
emitting records retain that behavior.
|
|
70
|
+
|
|
71
|
+
Workflow collection skips `dynamic/` paths. Missing workflow files are logged
|
|
72
|
+
and skipped without passing failed items to job or step parsing.
|
|
73
|
+
|
|
74
|
+
Rate-limit responses and GitHub App token refresh retain their existing retry
|
|
75
|
+
handling and do not consume the transient-failure budget. These timeouts are
|
|
76
|
+
not an overall request deadline. App authentication requests and credential
|
|
77
|
+
inventory CSV downloads retain their separate timeout settings.
|
|
78
|
+
|
|
58
79
|
### Enterprise SCIM and hybrid correlations
|
|
59
80
|
|
|
60
81
|
A token with enterprise SCIM access is used to collect both `/scim/v2/enterprises/{enterprise}/Users` and `/scim/v2/enterprises/{enterprise}/Groups`. The collector emits normalized `SCIM_Organization`, `SCIM_User`, and `SCIM_Group` nodes plus `SCIM_Contains`, `SCIM_MemberOf`, and `SCIM_Provisioned` relationships. Install the BloodHound SCIM extension alongside this extension to register the shared SCIM kinds.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
from dlt.sources.helpers import requests
|
|
7
|
+
from requests.exceptions import ChunkedEncodingError, ConnectionError, Timeout
|
|
8
|
+
from tenacity import RetryCallState
|
|
9
|
+
from tenacity.stop import stop_base
|
|
10
|
+
|
|
11
|
+
from openhound_github.github_retry import should_retry_github_response
|
|
12
|
+
|
|
13
|
+
__all__ = ["github_http_session", "should_retry_github_response"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class GraphQLPageRetryBudget:
|
|
18
|
+
"""Share transient failures across page sizes without changing other requests."""
|
|
19
|
+
|
|
20
|
+
failures: int = 0
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def exhausted(self) -> bool:
|
|
24
|
+
return self.failures >= 3
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def activate(self):
|
|
28
|
+
token = _graphql_page_budget.set(self)
|
|
29
|
+
try:
|
|
30
|
+
yield
|
|
31
|
+
finally:
|
|
32
|
+
_graphql_page_budget.reset(token)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_graphql_page_budget: ContextVar[GraphQLPageRetryBudget | None] = ContextVar(
|
|
36
|
+
"github_graphql_page_budget", default=None
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _StopAfterTransientFailures(stop_base):
|
|
41
|
+
"""Count transient failures per send, independently of other retry reasons."""
|
|
42
|
+
|
|
43
|
+
def __call__(self, retry_state: RetryCallState) -> bool:
|
|
44
|
+
outcome = retry_state.outcome
|
|
45
|
+
if outcome is None:
|
|
46
|
+
return False
|
|
47
|
+
exception = outcome.exception()
|
|
48
|
+
response = None
|
|
49
|
+
if isinstance(exception, requests.HTTPError):
|
|
50
|
+
response = exception.response
|
|
51
|
+
elif exception is None:
|
|
52
|
+
response = outcome.result()
|
|
53
|
+
transient = isinstance(exception, (ConnectionError, Timeout, ChunkedEncodingError)) or (
|
|
54
|
+
response is not None and 500 <= response.status_code < 600
|
|
55
|
+
)
|
|
56
|
+
page_budget = _graphql_page_budget.get()
|
|
57
|
+
if page_budget is not None:
|
|
58
|
+
page_budget.failures += int(transient)
|
|
59
|
+
# Return gateway errors immediately so pagination can shrink the page.
|
|
60
|
+
return page_budget.exhausted or (
|
|
61
|
+
response is not None and response.status_code in (502, 504)
|
|
62
|
+
)
|
|
63
|
+
failures = getattr(retry_state, "_github_transient_failures", 0) + int(transient)
|
|
64
|
+
setattr(retry_state, "_github_transient_failures", failures)
|
|
65
|
+
return failures >= 3
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def github_http_session(retry_condition):
|
|
69
|
+
"""Bound transient failures while retaining DLT's existing retry policy."""
|
|
70
|
+
session = requests.Client(
|
|
71
|
+
request_timeout=(10, 30),
|
|
72
|
+
status_codes=tuple(range(500, 600)),
|
|
73
|
+
retry_condition=retry_condition,
|
|
74
|
+
).session
|
|
75
|
+
# Keep the configured overall limit for rate limits and credential refresh.
|
|
76
|
+
# RetryCallState is new for every send, including concurrent requests.
|
|
77
|
+
retry = session.send.retry
|
|
78
|
+
retry.stop = _StopAfterTransientFailures() | retry.stop
|
|
79
|
+
send = session.send
|
|
80
|
+
|
|
81
|
+
@wraps(send)
|
|
82
|
+
def send_with_status_check(request, **kwargs):
|
|
83
|
+
response = send(request, **kwargs)
|
|
84
|
+
# RESTClient calls send directly, bypassing Session.request's status check.
|
|
85
|
+
# Raise only after the retry loop finishes so scope handlers can skip it.
|
|
86
|
+
response.raise_for_status()
|
|
87
|
+
return response
|
|
88
|
+
|
|
89
|
+
session.send = send_with_status_check
|
|
90
|
+
return session
|
|
@@ -14,6 +14,7 @@ from dlt.sources.helpers.rest_client.paginators import (
|
|
|
14
14
|
from requests import Request
|
|
15
15
|
|
|
16
16
|
from openhound_github.auth import GitHubAppInstallationAuth
|
|
17
|
+
from openhound_github.github_rest_client import GraphQLPageRetryBudget
|
|
17
18
|
from openhound_github.github_retry import (
|
|
18
19
|
is_primary_rate_limit_response,
|
|
19
20
|
is_secondary_rate_limit_response,
|
|
@@ -230,9 +231,9 @@ def adaptive_graphql_paginate(
|
|
|
230
231
|
) -> Iterator[dict[str, Any]]:
|
|
231
232
|
"""Yield GraphQL pages while adapting connection size after gateway failures.
|
|
232
233
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
HTTP retries and smaller-page recovery share three transient attempts per
|
|
235
|
+
page. Gateway errors return immediately so the next attempt can shrink the
|
|
236
|
+
page; rate-limit and credential-refresh retries retain their existing policy.
|
|
236
237
|
"""
|
|
237
238
|
if not page_sizes or any(page_size <= 0 for page_size in page_sizes):
|
|
238
239
|
raise ValueError("page_sizes must contain positive integers")
|
|
@@ -270,6 +271,7 @@ def adaptive_graphql_paginate(
|
|
|
270
271
|
page_data: dict[str, Any] | None = None
|
|
271
272
|
paginator: GraphQLCursorPaginator | None = None
|
|
272
273
|
successful_page_size: int | None = None
|
|
274
|
+
retry_budget = GraphQLPageRetryBudget()
|
|
273
275
|
|
|
274
276
|
for index, page_size in enumerate(candidate_sizes):
|
|
275
277
|
request_variables = {
|
|
@@ -278,10 +280,11 @@ def adaptive_graphql_paginate(
|
|
|
278
280
|
page_size_variable: page_size,
|
|
279
281
|
}
|
|
280
282
|
try:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
with retry_budget.activate():
|
|
284
|
+
response = client.post(
|
|
285
|
+
graphql_path,
|
|
286
|
+
json={"query": query, "variables": request_variables},
|
|
287
|
+
)
|
|
285
288
|
response.raise_for_status()
|
|
286
289
|
response_json = response.json()
|
|
287
290
|
paginator = GraphQLCursorPaginator(
|
|
@@ -303,7 +306,11 @@ def adaptive_graphql_paginate(
|
|
|
303
306
|
status_code = _graphql_gateway_status(error)
|
|
304
307
|
next_page_size = (
|
|
305
308
|
candidate_sizes[index + 1]
|
|
306
|
-
if
|
|
309
|
+
if (
|
|
310
|
+
status_code is not None
|
|
311
|
+
and index + 1 < len(candidate_sizes)
|
|
312
|
+
and not retry_budget.exhausted
|
|
313
|
+
)
|
|
307
314
|
else None
|
|
308
315
|
)
|
|
309
316
|
if next_page_size is not None:
|
{openhound_github-0.22.0 → openhound_github-0.22.1}/src/openhound_github/resources/organization.py
RENAMED
|
@@ -6,6 +6,7 @@ from threading import Lock
|
|
|
6
6
|
from typing import Any, Iterator
|
|
7
7
|
from urllib.parse import quote
|
|
8
8
|
|
|
9
|
+
import dlt
|
|
9
10
|
from dlt.sources.helpers import requests
|
|
10
11
|
from dlt.sources.helpers.rest_client.client import RESTClient
|
|
11
12
|
from dlt.sources.helpers.rest_client.paginators import (
|
|
@@ -1475,7 +1476,7 @@ def workflows(repo: Repository, ctx: SourceContext):
|
|
|
1475
1476
|
Workflow (Workflow): An active workflow record.
|
|
1476
1477
|
"""
|
|
1477
1478
|
|
|
1478
|
-
@
|
|
1479
|
+
@dlt.defer
|
|
1479
1480
|
def _workflow_file_contents(
|
|
1480
1481
|
client: RESTClient,
|
|
1481
1482
|
repo: Repository,
|
|
@@ -1487,11 +1488,30 @@ def workflows(repo: Repository, ctx: SourceContext):
|
|
|
1487
1488
|
return None
|
|
1488
1489
|
|
|
1489
1490
|
params = {"ref": repo.default_branch} if repo.default_branch else None
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1491
|
+
try:
|
|
1492
|
+
response = client.get(
|
|
1493
|
+
f"/repos/{repo.full_name}/contents/{path}", params=params
|
|
1494
|
+
).json()
|
|
1495
|
+
content = response.get("content")
|
|
1496
|
+
except Exception as exc:
|
|
1497
|
+
# Return None so DLT drops the failed item. app.defer returns [],
|
|
1498
|
+
# which can reach the job/step transformers as an invalid Workflow.
|
|
1499
|
+
if (
|
|
1500
|
+
isinstance(exc, requests.HTTPError)
|
|
1501
|
+
and exc.response is not None
|
|
1502
|
+
and exc.response.status_code == 404
|
|
1503
|
+
):
|
|
1504
|
+
logger.warning(
|
|
1505
|
+
"Skipping workflow '%s' in repository '%s': file unavailable at ref '%s' (HTTP 404)",
|
|
1506
|
+
path, repo.full_name, repo.default_branch,
|
|
1507
|
+
)
|
|
1508
|
+
else:
|
|
1509
|
+
logger.error(
|
|
1510
|
+
"Unable to collect workflow '%s' in repository '%s': %s",
|
|
1511
|
+
path, repo.full_name, exc,
|
|
1512
|
+
)
|
|
1513
|
+
return None
|
|
1493
1514
|
|
|
1494
|
-
content = response.get("content")
|
|
1495
1515
|
if not content:
|
|
1496
1516
|
return None
|
|
1497
1517
|
|
|
@@ -1517,6 +1537,9 @@ def workflows(repo: Repository, ctx: SourceContext):
|
|
|
1517
1537
|
):
|
|
1518
1538
|
for workflow in page:
|
|
1519
1539
|
if workflow.get("state") == "active":
|
|
1540
|
+
path = workflow.get("path")
|
|
1541
|
+
if not path or path.startswith("dynamic/"):
|
|
1542
|
+
continue
|
|
1520
1543
|
if workflow_permissions is None:
|
|
1521
1544
|
workflow_permissions = _repository_workflow_permissions(
|
|
1522
1545
|
ctx, client, repo.full_name
|
|
@@ -8,7 +8,6 @@ from urllib.parse import urlparse
|
|
|
8
8
|
import dlt
|
|
9
9
|
from dlt.common.configuration import configspec
|
|
10
10
|
from dlt.common.configuration.specs import CredentialsConfiguration
|
|
11
|
-
from dlt.sources.helpers import requests
|
|
12
11
|
from dlt.sources.helpers.rest_client.auth import AuthConfigBase
|
|
13
12
|
from dlt.sources.helpers.rest_client.auth import BearerTokenAuth
|
|
14
13
|
from dlt.sources.helpers.rest_client.client import RESTClient
|
|
@@ -27,6 +26,7 @@ from openhound_github.helpers import (
|
|
|
27
26
|
DEFAULT_GITHUB_REST_API_URL,
|
|
28
27
|
github_retry_policy,
|
|
29
28
|
)
|
|
29
|
+
from openhound_github.github_rest_client import github_http_session
|
|
30
30
|
from openhound_github.main import app
|
|
31
31
|
from openhound_github.models.saml_helpers import (
|
|
32
32
|
DEFAULT_GITHUB_DEPLOYMENT_ID,
|
|
@@ -352,10 +352,7 @@ def source(
|
|
|
352
352
|
},
|
|
353
353
|
auth=auth,
|
|
354
354
|
paginator=HeaderLinkPaginator(),
|
|
355
|
-
session=
|
|
356
|
-
status_codes=tuple(range(500, 600)),
|
|
357
|
-
retry_condition=github_retry_policy(auth),
|
|
358
|
-
).session,
|
|
355
|
+
session=github_http_session(github_retry_policy(auth)),
|
|
359
356
|
)
|
|
360
357
|
|
|
361
358
|
def clients(auth: AuthConfigBase) -> tuple[RESTClient, RESTClient]:
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Exercise exhausted retries through the real parallel DLT resource wrappers."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from threading import Lock
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
from urllib.parse import parse_qs, urlparse
|
|
9
|
+
|
|
10
|
+
import dlt
|
|
11
|
+
import pytest
|
|
12
|
+
from dlt.sources.helpers.rest_client.auth import BearerTokenAuth
|
|
13
|
+
from dlt.sources.helpers.rest_client.client import RESTClient
|
|
14
|
+
from dlt.sources.helpers.rest_client.paginators import HeaderLinkPaginator
|
|
15
|
+
from requests import Response
|
|
16
|
+
from requests.exceptions import ReadTimeout
|
|
17
|
+
|
|
18
|
+
from openhound_github.github_rest_client import github_http_session
|
|
19
|
+
from openhound_github.helpers import github_retry_policy
|
|
20
|
+
from openhound_github.resources.organization import (
|
|
21
|
+
OrgContext,
|
|
22
|
+
SourceContext,
|
|
23
|
+
environment_branch_policies,
|
|
24
|
+
environment_secrets,
|
|
25
|
+
environment_variables,
|
|
26
|
+
environments,
|
|
27
|
+
organizations,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _client(monkeypatch, route):
|
|
32
|
+
session = github_http_session(github_retry_policy(BearerTokenAuth("test")))
|
|
33
|
+
session.send.retry.sleep = lambda _: None
|
|
34
|
+
calls = Counter()
|
|
35
|
+
lock = Lock()
|
|
36
|
+
|
|
37
|
+
def send(request, **kwargs):
|
|
38
|
+
url = urlparse(request.url)
|
|
39
|
+
page = parse_qs(url.query).get("page", ["1"])[0]
|
|
40
|
+
with lock:
|
|
41
|
+
calls[url.path, page] += 1
|
|
42
|
+
outcome = route(url.path, page)
|
|
43
|
+
if isinstance(outcome, Exception):
|
|
44
|
+
raise outcome
|
|
45
|
+
status, payload, more = outcome
|
|
46
|
+
response = Response()
|
|
47
|
+
response.status_code = status
|
|
48
|
+
response.url = request.url
|
|
49
|
+
response.request = request
|
|
50
|
+
response._content = json.dumps(payload).encode()
|
|
51
|
+
if more:
|
|
52
|
+
response.headers["Link"] = f'<https://api.github.com{url.path}?page=2>; rel="next"'
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
monkeypatch.setattr(session.get_adapter("https://"), "send", send)
|
|
56
|
+
return RESTClient(
|
|
57
|
+
"https://api.github.com", session=session, paginator=HeaderLinkPaginator(),
|
|
58
|
+
), calls
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _failure(error):
|
|
62
|
+
return ReadTimeout("simulated read timeout") if error == "timeout" else (
|
|
63
|
+
503, {"message": "simulated unavailable endpoint"}, False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _field(row, name):
|
|
68
|
+
return row[name] if isinstance(row, dict) else getattr(row, name)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.parametrize("error", ["503", "timeout"])
|
|
72
|
+
def test_later_organizations_collect_after_exhausted_retries(monkeypatch, caplog, error):
|
|
73
|
+
def route(path, page):
|
|
74
|
+
if path == "/orgs/bad":
|
|
75
|
+
return _failure(error)
|
|
76
|
+
org = path.split("/")[2]
|
|
77
|
+
return 200, {"login": org, "node_id": f"ORG_{org}"}, False
|
|
78
|
+
|
|
79
|
+
client, calls = _client(monkeypatch, route)
|
|
80
|
+
ctx = SourceContext(client=client, organizations=[
|
|
81
|
+
OrgContext(client=client, org_name=name) for name in ("first", "bad", "last")
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
with caplog.at_level(logging.ERROR):
|
|
85
|
+
rows = list(organizations(ctx))
|
|
86
|
+
|
|
87
|
+
assert {_field(row, "login") for row in rows} == {"first", "last"}
|
|
88
|
+
assert calls["/orgs/bad", "1"] == 3
|
|
89
|
+
assert "processing organization 'bad'" in caplog.text
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize("error", ["503", "timeout"])
|
|
93
|
+
def test_later_repositories_collect_and_partial_page_is_retained(monkeypatch, caplog, error):
|
|
94
|
+
def route(path, page):
|
|
95
|
+
repo = path.split("/")[3]
|
|
96
|
+
if repo == "bad" and page == "2":
|
|
97
|
+
return _failure(error)
|
|
98
|
+
environment = {
|
|
99
|
+
"id": 1, "node_id": f"ENV_{repo}", "name": repo,
|
|
100
|
+
"url": "https://api.github.com/example", "html_url": "https://github.com/example",
|
|
101
|
+
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
|
|
102
|
+
}
|
|
103
|
+
return 200, {"environments": [environment]}, repo == "bad"
|
|
104
|
+
|
|
105
|
+
client, calls = _client(monkeypatch, route)
|
|
106
|
+
ctx = SourceContext(client=client, organizations=[OrgContext(client=client, org_name="org")])
|
|
107
|
+
parents = dlt.resource([
|
|
108
|
+
SimpleNamespace(id=f"REPO_{name}", name=name, org_login="org", environment_count=1)
|
|
109
|
+
for name in ("first", "bad", "last")
|
|
110
|
+
], name="probe_repositories")
|
|
111
|
+
|
|
112
|
+
with caplog.at_level(logging.ERROR):
|
|
113
|
+
rows = list(parents | environments(ctx))
|
|
114
|
+
|
|
115
|
+
assert {_field(row, "repository_name") for row in rows} == {"first", "bad", "last"}
|
|
116
|
+
assert calls["/repos/org/bad/environments", "2"] == 3
|
|
117
|
+
assert "resource 'environments'" in caplog.text
|
|
118
|
+
assert "/repos/org/bad/environments" in caplog.text or error == "timeout"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.parametrize("transformer, suffix, selector", [
|
|
122
|
+
(environment_variables, "variables", "variables"),
|
|
123
|
+
(environment_secrets, "secrets", "secrets"),
|
|
124
|
+
(environment_branch_policies, "deployment-branch-policies", "branch_policies"),
|
|
125
|
+
])
|
|
126
|
+
@pytest.mark.parametrize("error", ["503", "timeout"])
|
|
127
|
+
def test_later_environments_collect_after_partial_failure(
|
|
128
|
+
monkeypatch, caplog, transformer, suffix, selector, error,
|
|
129
|
+
):
|
|
130
|
+
def route(path, page):
|
|
131
|
+
name = path.split("/")[5]
|
|
132
|
+
if name == "bad" and page == "2":
|
|
133
|
+
return _failure(error)
|
|
134
|
+
item = {"name": "item", "value": "value", "id": 1, "node_id": "POLICY",
|
|
135
|
+
"created_at": "2026-01-01T00:00:00Z"}
|
|
136
|
+
return 200, {selector: [item]}, name == "bad"
|
|
137
|
+
|
|
138
|
+
client, calls = _client(monkeypatch, route)
|
|
139
|
+
ctx = SourceContext(client=client, organizations=[OrgContext(client=client, org_name="org")])
|
|
140
|
+
parents = dlt.resource([
|
|
141
|
+
SimpleNamespace(
|
|
142
|
+
name=name, node_id=f"ENV_{name}", org_login="org", repository_name="repo",
|
|
143
|
+
repository_full_name="org/repo", repository_node_id="REPO",
|
|
144
|
+
has_custom_branch_policies=True, required_reviewers=False, prevent_self_review=False,
|
|
145
|
+
) for name in ("first", "bad", "last")
|
|
146
|
+
], name="probe_environments")
|
|
147
|
+
|
|
148
|
+
with caplog.at_level(logging.ERROR):
|
|
149
|
+
rows = list(parents | transformer(ctx))
|
|
150
|
+
|
|
151
|
+
assert {_field(row, "environment_name") for row in rows} == {"first", "bad", "last"}
|
|
152
|
+
assert calls[f"/repos/org/repo/environments/bad/{suffix}", "2"] == 3
|
|
153
|
+
assert f"resource '{transformer.name}'" in caplog.text
|
|
@@ -4,10 +4,12 @@ from datetime import datetime, timedelta, timezone
|
|
|
4
4
|
from threading import Event, Lock
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
|
+
import pytest
|
|
7
8
|
from dlt.sources.helpers.rest_client.auth import BearerTokenAuth
|
|
8
9
|
|
|
9
10
|
from openhound_github.auth import GitHubAppInstallationAuth, TokenResponse
|
|
10
11
|
from openhound_github.helpers import github_retry_policy
|
|
12
|
+
from openhound_github.github_rest_client import github_http_session
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class FakeInstallation:
|
|
@@ -243,3 +245,37 @@ def test_retry_policy_does_not_repair_bad_credentials_for_bearer_token_auth() ->
|
|
|
243
245
|
|
|
244
246
|
assert should_retry is False
|
|
245
247
|
assert request.headers["Authorization"] == "Bearer static-token"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@pytest.mark.parametrize("replacement_rejected", [False, True])
|
|
251
|
+
def test_http_session_retries_with_refreshed_credentials(monkeypatch, replacement_rejected):
|
|
252
|
+
installation = FakeInstallation("new-token")
|
|
253
|
+
auth = GitHubAppInstallationAuth(installation=installation)
|
|
254
|
+
auth.access_token = "old-token"
|
|
255
|
+
auth.expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
|
|
256
|
+
session = github_http_session(github_retry_policy(auth))
|
|
257
|
+
session.auth = auth
|
|
258
|
+
session.send.retry.sleep = lambda _: None
|
|
259
|
+
# Two transient failures must leave room for credential recovery.
|
|
260
|
+
statuses = iter([503, 503, 401, 401 if replacement_rejected else 200])
|
|
261
|
+
authorizations = []
|
|
262
|
+
|
|
263
|
+
def send(request, **kwargs):
|
|
264
|
+
authorizations.append(request.headers.get("Authorization"))
|
|
265
|
+
response = requests.Response()
|
|
266
|
+
response.status_code = next(statuses)
|
|
267
|
+
response.request = request
|
|
268
|
+
response.url = request.url
|
|
269
|
+
response._content = b'{"message":"Bad credentials"}'
|
|
270
|
+
return response
|
|
271
|
+
|
|
272
|
+
monkeypatch.setattr(session.get_adapter("https://"), "send", send)
|
|
273
|
+
if replacement_rejected:
|
|
274
|
+
with pytest.raises(requests.HTTPError) as caught:
|
|
275
|
+
session.get("https://api.github.com/example")
|
|
276
|
+
assert caught.value.response.status_code == 401
|
|
277
|
+
else:
|
|
278
|
+
assert session.get("https://api.github.com/example").status_code == 200
|
|
279
|
+
|
|
280
|
+
assert authorizations == ["Bearer old-token"] * 3 + ["Bearer new-token"]
|
|
281
|
+
assert installation.token_calls == 1
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import Mock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from dlt.sources.helpers import requests as dlt_requests
|
|
6
|
+
from requests import HTTPError, Response
|
|
7
|
+
from requests.exceptions import ChunkedEncodingError, ConnectionError, ReadTimeout
|
|
8
|
+
from dlt.sources.helpers.rest_client.auth import BearerTokenAuth
|
|
9
|
+
|
|
10
|
+
from openhound_github.github_rest_client import github_http_session, should_retry_github_response
|
|
11
|
+
from openhound_github.helpers import github_retry_policy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_response(
|
|
15
|
+
status_code: int,
|
|
16
|
+
*,
|
|
17
|
+
headers: dict[str, str] | None = None,
|
|
18
|
+
body: dict[str, object] | None = None,
|
|
19
|
+
text: str | None = None,
|
|
20
|
+
) -> Response:
|
|
21
|
+
response = Response()
|
|
22
|
+
response.status_code = status_code
|
|
23
|
+
response.headers.update(headers or {})
|
|
24
|
+
if body is not None:
|
|
25
|
+
response._content = json.dumps(body).encode("utf-8")
|
|
26
|
+
response.headers.setdefault("Content-Type", "application/json")
|
|
27
|
+
else:
|
|
28
|
+
response._content = (text or "").encode("utf-8")
|
|
29
|
+
response.url = "https://api.github.com/example"
|
|
30
|
+
return response
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_retries_primary_rate_limit_response() -> None:
|
|
34
|
+
response = make_response(
|
|
35
|
+
403,
|
|
36
|
+
headers={"x-ratelimit-remaining": "0"},
|
|
37
|
+
body={"message": "API rate limit exceeded"},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
assert should_retry_github_response(response, None) is True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_retries_secondary_rate_limit_response() -> None:
|
|
44
|
+
response = make_response(
|
|
45
|
+
429,
|
|
46
|
+
body={"message": "You have exceeded a secondary rate limit."},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
assert should_retry_github_response(response, None) is True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_does_not_retry_non_rate_limit_403() -> None:
|
|
53
|
+
response = make_response(
|
|
54
|
+
403,
|
|
55
|
+
body={"message": "Resource not accessible by integration"},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert should_retry_github_response(response, None) is False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _session(monkeypatch, outcomes):
|
|
62
|
+
session = github_http_session(github_retry_policy(BearerTokenAuth("test")))
|
|
63
|
+
send = Mock(side_effect=outcomes)
|
|
64
|
+
monkeypatch.setattr(session.get_adapter("https://"), "send", send)
|
|
65
|
+
session.send.retry.sleep = lambda _: None
|
|
66
|
+
return session, send
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("status", [500, 502, 503, 504])
|
|
70
|
+
def test_server_failures_stop_after_three_attempts(monkeypatch, status):
|
|
71
|
+
session, send = _session(monkeypatch, [make_response(status) for _ in range(4)])
|
|
72
|
+
|
|
73
|
+
with pytest.raises(HTTPError) as caught:
|
|
74
|
+
session.get("https://api.github.com/example")
|
|
75
|
+
|
|
76
|
+
assert caught.value.response.status_code == status
|
|
77
|
+
assert send.call_count == 3
|
|
78
|
+
assert all(call.kwargs["timeout"] == (10, 30) for call in send.call_args_list)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("error", [ReadTimeout, ConnectionError, ChunkedEncodingError])
|
|
82
|
+
def test_network_failures_stop_after_three_attempts(monkeypatch, error):
|
|
83
|
+
session, send = _session(monkeypatch, [error("interrupted") for _ in range(4)])
|
|
84
|
+
|
|
85
|
+
with pytest.raises(error):
|
|
86
|
+
session.get("https://api.github.com/example")
|
|
87
|
+
|
|
88
|
+
assert send.call_count == 3
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_transient_failure_count_resets_for_each_request(monkeypatch):
|
|
92
|
+
session, send = _session(monkeypatch, [
|
|
93
|
+
make_response(503), make_response(503), make_response(200),
|
|
94
|
+
make_response(503), make_response(503), make_response(200),
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
assert session.get("https://api.github.com/example").status_code == 200
|
|
98
|
+
assert session.get("https://api.github.com/example").status_code == 200
|
|
99
|
+
assert send.call_count == 6
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_mixed_server_and_network_failures_share_budget(monkeypatch):
|
|
103
|
+
session, send = _session(monkeypatch, [
|
|
104
|
+
make_response(503), ReadTimeout("interrupted"), make_response(502),
|
|
105
|
+
make_response(200),
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
with pytest.raises(HTTPError):
|
|
109
|
+
session.get("https://api.github.com/example")
|
|
110
|
+
|
|
111
|
+
assert send.call_count == 3
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_rate_limit_retries_do_not_consume_transient_budget(monkeypatch):
|
|
115
|
+
session, send = _session(monkeypatch, [
|
|
116
|
+
*[make_response(429, body={"message": "secondary rate limit"}) for _ in range(4)],
|
|
117
|
+
make_response(503), make_response(503), make_response(200),
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
assert session.get("https://api.github.com/example").status_code == 200
|
|
121
|
+
assert send.call_count == 7
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.parametrize(("status", "headers", "body"), [
|
|
125
|
+
(403, {"x-ratelimit-remaining": "0", "x-ratelimit-reset": "4102444800"},
|
|
126
|
+
{"message": "API rate limit exceeded"}),
|
|
127
|
+
(429, {}, {"message": "secondary rate limit"}),
|
|
128
|
+
(429, {"Retry-After": "12"}, {"message": "Please wait"}),
|
|
129
|
+
(200, {"x-ratelimit-resource": "graphql", "x-ratelimit-remaining": "0",
|
|
130
|
+
"x-ratelimit-reset": "4102444800"},
|
|
131
|
+
{"errors": [{"message": "API rate limit exceeded"}]}),
|
|
132
|
+
])
|
|
133
|
+
def test_rate_limit_attempts_and_waits_match_existing_session(
|
|
134
|
+
monkeypatch, status, headers, body,
|
|
135
|
+
):
|
|
136
|
+
monkeypatch.setenv("RUNTIME__REQUEST_MAX_ATTEMPTS", "5")
|
|
137
|
+
observations = []
|
|
138
|
+
for updated in (False, True):
|
|
139
|
+
policy = github_retry_policy(BearerTokenAuth("test"))
|
|
140
|
+
session = (
|
|
141
|
+
github_http_session(policy) if updated else dlt_requests.Client(
|
|
142
|
+
status_codes=tuple(range(500, 600)), retry_condition=policy,
|
|
143
|
+
).session
|
|
144
|
+
)
|
|
145
|
+
send = Mock(side_effect=[
|
|
146
|
+
make_response(status, headers=headers, body=body) for _ in range(5)
|
|
147
|
+
])
|
|
148
|
+
monkeypatch.setattr(session.get_adapter("https://"), "send", send)
|
|
149
|
+
sleep = Mock()
|
|
150
|
+
session.send.retry.sleep = sleep
|
|
151
|
+
if status != 200:
|
|
152
|
+
with pytest.raises(HTTPError):
|
|
153
|
+
session.get("https://api.github.com/example")
|
|
154
|
+
else:
|
|
155
|
+
response = session.get("https://api.github.com/graphql")
|
|
156
|
+
assert response.json()["errors"]
|
|
157
|
+
observations.append((send.call_count, [float(c.args[0]) for c in sleep.call_args_list]))
|
|
158
|
+
|
|
159
|
+
assert observations[0][0] == 5
|
|
160
|
+
assert len(observations[0][1]) == 4
|
|
161
|
+
assert observations[1] == observations[0]
|