src-auth-perms-sync 0.8.0__tar.gz → 0.8.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.
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/PKG-INFO +2 -2
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/pyproject.toml +1 -1
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/cli.py +35 -17
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/integration/test_cli_entrypoint.py +2 -2
- src_auth_perms_sync-0.8.1/tests/live-maps-seed.yaml +80 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/run.py +178 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/setup.yaml +4 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_cli_config.py +131 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/uv.lock +4 -4
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.github/CODEOWNERS +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.github/workflows/ci.yml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.github/workflows/release.yml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.github/workflows/validate.yml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.gitignore +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.markdownlint-cli2.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/.python-version +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/AGENTS.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/LICENSE +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/README.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/SECURITY.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/TODO.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/audit-dead-code.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/engineering-requests.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/hooks/pre-commit +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/memory-analysis/mapping-efficiency.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/memory-analysis/memory-efficiency-analyze.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/memory-analysis/memory-efficiency-generate.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/memory-analysis/memory-efficiency-monitor-sourcegraph.sh +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/memory-analysis/memory-efficiency.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/dev/update-python-versions.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/examples/import.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/examples/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/package-lock.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/package.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/renovate.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/__main__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/orgs/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/orgs/command.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/orgs/queries.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/orgs/sync.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/orgs/types.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/apply.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/command.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/full_set.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/mapping.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/maps.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/queries.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/restore.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/snapshot.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/sourcegraph.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/types.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/permissions/workflow.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/backups.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/queries.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/run_context.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/saml_groups.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/site_config.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/sourcegraph.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/src/src_auth_perms_sync/shared/types.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/README.md +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/case_runner.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-by-email-and-list/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-by-email-and-list/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-by-email-and-list/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-preserves-existing/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-preserves-existing/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/add-users-preserves-existing/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/and-filters-intersect/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/and-filters-intersect/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/and-filters-intersect/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/empty-maps-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/empty-maps-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-dry-run/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-preserves-pending/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-preserves-pending/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-preserves-pending/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-unions/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-unions/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-unions/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-with-backup/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-with-backup/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/full-overwrite-with-backup/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/get-full-snapshot/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/get-repos-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/get-user-grants/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-bad-regex/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-bad-regex/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-missing-repos-section/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-missing-repos-section/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-restore-wrong-schema-version/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-restore-wrong-schema-version/snapshot.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-repos-created-after-date/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-repos-created-after-date/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-unknown-repo/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-unknown-repo/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-unknown-user/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-unknown-user/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-users-created-after-date/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-set-users-created-after-date/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-unknown-selector-field/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/invalid-unknown-selector-field/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/match-provider-and-host-fields/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/match-provider-and-host-fields/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/match-provider-and-host-fields/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/no-match-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/no-match-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/regex-filters-scope/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/regex-filters-scope/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/regex-filters-scope/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-applies-snapshot/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-applies-snapshot/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-applies-snapshot/snapshot.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-dry-run-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-dry-run-noop/snapshot.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-missing-file/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-restores-pending/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-restores-pending/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/restore-restores-pending/snapshot.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-filter/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-filter/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-filter/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-live/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-live/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/saml-group-live/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-full-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-created-after-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-filter/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-filter/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-filter/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-without-explicit-perms/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-without-explicit-perms/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-repos-without-perms-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-temp-user/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-temp-user/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-created-after-temp-user/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-noop/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-noop/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-scoped/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-scoped/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-sync-saml-orgs-scoped/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/set-users-without-perms-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/sync-saml-orgs-full-cleanup/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/sync-saml-orgs-full-cleanup/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/sync-saml-orgs-users-scoped/after.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/fixtures/sync-saml-orgs-users-scoped/before.json +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/e2e/test_local_cases.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/integration/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/setup.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/tests.yaml +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unicode_scan.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/__init__.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_apply.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_backups.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_command_additive.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_command_files.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_mapping_rules.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_maps.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_module_api.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_permissions_sourcegraph.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_restore.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_saml_groups.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_snapshot.py +0 -0
- {src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/unit/test_unicode_scan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: src-auth-perms-sync
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Set Sourcegraph permissions from authentication provider data
|
|
5
5
|
Project-URL: Homepage, https://github.com/sourcegraph/src-auth-perms-sync
|
|
6
6
|
Project-URL: Issues, https://github.com/sourcegraph/src-auth-perms-sync/issues
|
|
@@ -18,7 +18,7 @@ Classifier: Typing :: Typed
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Requires-Dist: json5>=0.15.0
|
|
20
20
|
Requires-Dist: pyyaml>=6.0.3
|
|
21
|
-
Requires-Dist: src-py-lib[otel]==0.3.
|
|
21
|
+
Requires-Dist: src-py-lib[otel]==0.3.2
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
24
24
|
# src-auth-perms-sync
|
|
@@ -263,23 +263,23 @@ class Config(src.SourcegraphClientConfig, src.LoggingConfig, src.OpenTelemetryCo
|
|
|
263
263
|
"NOTE: This can be CPU intensive on the database server,\n"
|
|
264
264
|
"Reduce --parallelism if instance performance is impacted"
|
|
265
265
|
),
|
|
266
|
-
help_group="
|
|
266
|
+
help_group="Scope (required: 1 of the following)",
|
|
267
267
|
)
|
|
268
268
|
users: tuple[str, ...] = src.config_field(
|
|
269
269
|
default=(),
|
|
270
270
|
env_var="SRC_AUTH_PERMS_SYNC_USERS",
|
|
271
271
|
cli_flag="--users",
|
|
272
272
|
metavar="USERS",
|
|
273
|
-
help="
|
|
274
|
-
help_group="
|
|
273
|
+
help="Filter on a comma-delimited list of Sourcegraph usernames and/or email addresses",
|
|
274
|
+
help_group="Scope (required: 1 of the following)",
|
|
275
275
|
)
|
|
276
276
|
users_without_explicit_perms: bool = src.config_field(
|
|
277
277
|
default=False,
|
|
278
278
|
env_var="SRC_AUTH_PERMS_SYNC_USERS_WITHOUT_EXPLICIT_PERMS",
|
|
279
279
|
cli_flag="--users-without-explicit-perms",
|
|
280
280
|
cli_action="store_true",
|
|
281
|
-
help="
|
|
282
|
-
help_group="
|
|
281
|
+
help="Filter on Sourcegraph users without explicit permissions",
|
|
282
|
+
help_group="Scope (required: 1 of the following)",
|
|
283
283
|
)
|
|
284
284
|
users_created_after: str | None = src.config_field(
|
|
285
285
|
default=None,
|
|
@@ -287,24 +287,24 @@ class Config(src.SourcegraphClientConfig, src.LoggingConfig, src.OpenTelemetryCo
|
|
|
287
287
|
cli_flag="--users-created-after",
|
|
288
288
|
metavar="YYYY-MM-DD",
|
|
289
289
|
pattern=r"^\d{4}-\d{2}-\d{2}$",
|
|
290
|
-
help="
|
|
291
|
-
help_group="
|
|
290
|
+
help="Filter on Sourcegraph users created on or after this date",
|
|
291
|
+
help_group="Scope (required: 1 of the following)",
|
|
292
292
|
)
|
|
293
293
|
repos: tuple[str, ...] = src.config_field(
|
|
294
294
|
default=(),
|
|
295
295
|
env_var="SRC_AUTH_PERMS_SYNC_REPOS",
|
|
296
296
|
cli_flag="--repos",
|
|
297
297
|
metavar="REPOS",
|
|
298
|
-
help="
|
|
299
|
-
help_group="
|
|
298
|
+
help="Filter on a comma-delimited list of Sourcegraph repository names",
|
|
299
|
+
help_group="Scope (required: 1 of the following)",
|
|
300
300
|
)
|
|
301
301
|
repos_without_explicit_perms: bool = src.config_field(
|
|
302
302
|
default=False,
|
|
303
303
|
env_var="SRC_AUTH_PERMS_SYNC_REPOS_WITHOUT_EXPLICIT_PERMS",
|
|
304
304
|
cli_flag="--repos-without-explicit-perms",
|
|
305
305
|
cli_action="store_true",
|
|
306
|
-
help="
|
|
307
|
-
help_group="
|
|
306
|
+
help="Filter on repositories without explicit permissions",
|
|
307
|
+
help_group="Scope (required: 1 of the following)",
|
|
308
308
|
)
|
|
309
309
|
repos_created_after: str | None = src.config_field(
|
|
310
310
|
default=None,
|
|
@@ -312,8 +312,8 @@ class Config(src.SourcegraphClientConfig, src.LoggingConfig, src.OpenTelemetryCo
|
|
|
312
312
|
cli_flag="--repos-created-after",
|
|
313
313
|
metavar="YYYY-MM-DD",
|
|
314
314
|
pattern=r"^\d{4}-\d{2}-\d{2}$",
|
|
315
|
-
help="
|
|
316
|
-
help_group="
|
|
315
|
+
help="Filter on repositories cloned to the Sourcegraph instance on or after this date",
|
|
316
|
+
help_group="Scope (required: 1 of the following)",
|
|
317
317
|
)
|
|
318
318
|
sync_saml_orgs: bool = src.config_field(
|
|
319
319
|
default=False,
|
|
@@ -404,28 +404,40 @@ CLI_COMMANDS: tuple[CliCommand, ...] = (
|
|
|
404
404
|
argument_name="get",
|
|
405
405
|
command_name="get",
|
|
406
406
|
help="Discover auth providers and code hosts",
|
|
407
|
-
description=
|
|
407
|
+
description=(
|
|
408
|
+
"Gather auth providers, code hosts, users, and permissions.\n"
|
|
409
|
+
"NOTE: All args can be set via cli args or environment variables"
|
|
410
|
+
),
|
|
408
411
|
config_fields=GET_CONFIG_FIELDS,
|
|
409
412
|
),
|
|
410
413
|
CliCommand(
|
|
411
414
|
argument_name="set",
|
|
412
415
|
command_name="set",
|
|
413
416
|
help="Reconcile repo permissions from maps.yaml",
|
|
414
|
-
description=
|
|
417
|
+
description=(
|
|
418
|
+
"Reconcile Sourcegraph explicit repo permissions from maps.yaml\n"
|
|
419
|
+
"NOTE: All args can be set via cli args or environment variables"
|
|
420
|
+
),
|
|
415
421
|
config_fields=SET_CONFIG_FIELDS,
|
|
416
422
|
),
|
|
417
423
|
CliCommand(
|
|
418
424
|
argument_name="restore",
|
|
419
425
|
command_name="restore",
|
|
420
426
|
help="Restore repo permissions from a snapshot",
|
|
421
|
-
description=
|
|
427
|
+
description=(
|
|
428
|
+
"Restore Sourcegraph explicit repo permissions from a snapshot JSON file.\n"
|
|
429
|
+
"NOTE: All args can be set via cli args or environment variables"
|
|
430
|
+
),
|
|
422
431
|
config_fields=RESTORE_CONFIG_FIELDS,
|
|
423
432
|
),
|
|
424
433
|
CliCommand(
|
|
425
434
|
argument_name="sync-saml-orgs",
|
|
426
435
|
command_name="sync_saml_orgs",
|
|
427
436
|
help="Sync orgs from SAML groups",
|
|
428
|
-
description=
|
|
437
|
+
description=(
|
|
438
|
+
"Create/update Sourcegraph organizations and memberships from SAML groups.\n"
|
|
439
|
+
"NOTE: All args can be set via cli args or environment variables"
|
|
440
|
+
),
|
|
429
441
|
config_fields=SYNC_SAML_ORGS_CONFIG_FIELDS,
|
|
430
442
|
),
|
|
431
443
|
)
|
|
@@ -690,6 +702,12 @@ def load_cli(argv: Sequence[str] | None = None) -> CliInput:
|
|
|
690
702
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
691
703
|
allow_abbrev=False,
|
|
692
704
|
)
|
|
705
|
+
parser.add_argument(
|
|
706
|
+
"--version",
|
|
707
|
+
action="version",
|
|
708
|
+
version=f"src-auth-perms-sync {_package_version()}",
|
|
709
|
+
help="Show the installed src-auth-perms-sync version and exit",
|
|
710
|
+
)
|
|
693
711
|
subparsers = parser.add_subparsers(
|
|
694
712
|
title="commands",
|
|
695
713
|
metavar="COMMAND",
|
{src_auth_perms_sync-0.8.0 → src_auth_perms_sync-0.8.1}/tests/integration/test_cli_entrypoint.py
RENAMED
|
@@ -85,9 +85,9 @@ class CliEntrypointTests(unittest.TestCase):
|
|
|
85
85
|
self.assertIn("--users USERS", set_help.stdout)
|
|
86
86
|
self.assertIn("--sync-saml-orgs", set_help.stdout)
|
|
87
87
|
self.assertNotIn("--restore-path", set_help.stdout)
|
|
88
|
-
self.assertIn("
|
|
88
|
+
self.assertIn("Scope (required: 1 of the following):", set_help.stdout)
|
|
89
89
|
self.assertLess(
|
|
90
|
-
set_help.stdout.index("\
|
|
90
|
+
set_help.stdout.index("\nScope (required:"),
|
|
91
91
|
set_help.stdout.index("\nOrganization sync:"),
|
|
92
92
|
)
|
|
93
93
|
self.assertNotIn("\nUser filters:", set_help.stdout)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Synthetic-corpus mapping rules used to bootstrap live test runs.
|
|
2
|
+
#
|
|
3
|
+
# The live harness (tests/run.py --live) writes this file to
|
|
4
|
+
# src-auth-perms-sync-runs/<endpoint>/maps.yaml when that path is missing or
|
|
5
|
+
# still holds the empty default template, so a fresh worktree can run the
|
|
6
|
+
# mutating permission cycles. An operator-maintained maps.yaml with real
|
|
7
|
+
# rules is left untouched.
|
|
8
|
+
#
|
|
9
|
+
# It targets the corpus that tests/setup.yaml provisions on the test
|
|
10
|
+
# instance:
|
|
11
|
+
# - users: test_user_00001 through test_user_10000
|
|
12
|
+
# - repos: test-repo-00001 through test-repo-50000
|
|
13
|
+
#
|
|
14
|
+
# Default active shape:
|
|
15
|
+
# - one large rectangular non-overlap rule: 10k users x 1k repos = 10M grants
|
|
16
|
+
# - two small overlapping rules near repo 49k to validate union correctness
|
|
17
|
+
#
|
|
18
|
+
# The 50k-repo all-repo selector below is intentionally not active by default:
|
|
19
|
+
# 10k users x 50k repos would produce 500M planned grants and very large
|
|
20
|
+
# dry-run after/diff files. Use it only for a deliberate long stress run.
|
|
21
|
+
|
|
22
|
+
x-test-users-all: &test_users_all
|
|
23
|
+
- '^test_user_[0-9]{5}$'
|
|
24
|
+
|
|
25
|
+
x-test-users-00001-00100: &test_users_00001_00100
|
|
26
|
+
- '^test_user_(0000[1-9]|000[1-9][0-9]|00100)$'
|
|
27
|
+
|
|
28
|
+
x-test-users-00101-00200: &test_users_00101_00200
|
|
29
|
+
- '^test_user_(0010[1-9]|001[1-9][0-9]|00200)$'
|
|
30
|
+
|
|
31
|
+
x-stress-code-host: &stress_code_host
|
|
32
|
+
kind: OTHER
|
|
33
|
+
displayName: 'OTHER #1'
|
|
34
|
+
url: http://src-serve-git:3434/
|
|
35
|
+
|
|
36
|
+
x-test-repos-00001-01000: &test_repos_00001_01000
|
|
37
|
+
- '^test-repo-(00[0-9]{3}|01000)$'
|
|
38
|
+
|
|
39
|
+
x-test-repos-49001-49100: &test_repos_49001_49100
|
|
40
|
+
- '^test-repo-(4900[1-9]|490[1-9][0-9]|49100)$'
|
|
41
|
+
|
|
42
|
+
x-test-repos-49051-49150: &test_repos_49051_49150
|
|
43
|
+
- '^test-repo-(4905[1-9]|490[6-9][0-9]|491[0-4][0-9]|49150)$'
|
|
44
|
+
|
|
45
|
+
x-test-repos-all-50000: &test_repos_all_50000
|
|
46
|
+
- '^test-repo-[0-4][0-9]{4}$'
|
|
47
|
+
- '^test-repo-50000$'
|
|
48
|
+
|
|
49
|
+
maps:
|
|
50
|
+
|
|
51
|
+
- name: MEMORY phase1 rectangle 10k users to first 1000 test repos
|
|
52
|
+
users:
|
|
53
|
+
usernameRegexes: *test_users_all
|
|
54
|
+
repos:
|
|
55
|
+
codeHostConnection: *stress_code_host
|
|
56
|
+
nameRegexes: *test_repos_00001_01000
|
|
57
|
+
|
|
58
|
+
- name: MEMORY overlap A first 100 users to repos 49001-49100
|
|
59
|
+
users:
|
|
60
|
+
usernameRegexes: *test_users_00001_00100
|
|
61
|
+
repos:
|
|
62
|
+
codeHostConnection: *stress_code_host
|
|
63
|
+
nameRegexes: *test_repos_49001_49100
|
|
64
|
+
|
|
65
|
+
- name: MEMORY overlap B next 100 users to repos 49051-49150
|
|
66
|
+
users:
|
|
67
|
+
usernameRegexes: *test_users_00101_00200
|
|
68
|
+
repos:
|
|
69
|
+
codeHostConnection: *stress_code_host
|
|
70
|
+
nameRegexes: *test_repos_49051_49150
|
|
71
|
+
|
|
72
|
+
# Deliberate heavy stress profile; uncomment as the only active map for a
|
|
73
|
+
# 500M-grant planning run. Expect very large dry-run artifact files.
|
|
74
|
+
#
|
|
75
|
+
# - name: MEMORY max rectangle 10k users to all 50000 test repos
|
|
76
|
+
# users:
|
|
77
|
+
# usernameRegexes: *test_users_all
|
|
78
|
+
# repos:
|
|
79
|
+
# codeHostConnection: *stress_code_host
|
|
80
|
+
# nameRegexes: *test_repos_all_50000
|
|
@@ -73,6 +73,19 @@ if TYPE_CHECKING:
|
|
|
73
73
|
from tests.e2e.case_runner import FixtureRunResult, FixtureState
|
|
74
74
|
|
|
75
75
|
FIXTURES_DIR = ROOT / "tests" / "e2e" / "fixtures"
|
|
76
|
+
# Canonical synthetic-corpus maps.yaml the live harness seeds into a fresh
|
|
77
|
+
# worktree's endpoint runs directory so the mutating permission cycles have a
|
|
78
|
+
# valid maps file to apply. See ensure_live_maps_seed().
|
|
79
|
+
LIVE_MAPS_SEED_PATH = ROOT / "tests" / "live-maps-seed.yaml"
|
|
80
|
+
# Live checks that reach the pgsql pod over `kubectl exec` (and so need a
|
|
81
|
+
# valid AWS SSO session). check_database_connectivity() probes the pod when
|
|
82
|
+
# any of these is selected and skips them up front when it is unreachable.
|
|
83
|
+
DATABASE_BACKED_LIVE_CHECKS = ("live: perms follow saml group change",)
|
|
84
|
+
# When the pod is unreachable and the run is interactive, the harness offers
|
|
85
|
+
# to refresh AWS SSO, then re-probes on this cadence until it succeeds.
|
|
86
|
+
AWS_SSO_LOGIN_TIMEOUT_SECONDS = 300
|
|
87
|
+
DATABASE_PROBE_RETRY_INTERVAL_SECONDS = 10
|
|
88
|
+
DATABASE_PROBE_RETRY_TIMEOUT_SECONDS = 600
|
|
76
89
|
TEST_LOGS_DIR = ROOT / "logs"
|
|
77
90
|
LOG_PATH_PATTERN = re.compile(r"Writing log events to (.+?/log\.json)\.")
|
|
78
91
|
STRUCTURED_EVENT_LINE_PATTERN = re.compile(r"^[.]*event=\S+\s*$")
|
|
@@ -880,6 +893,10 @@ class TestSuite:
|
|
|
880
893
|
test_user: str = ""
|
|
881
894
|
skipped_check_names: list[str] = field(default_factory=list[str])
|
|
882
895
|
filter_matched_count: int = 0
|
|
896
|
+
# Set by check_database_connectivity(); None until the pgsql pod is probed.
|
|
897
|
+
# False means DB-backed live tests are skipped (the recorded probe failure
|
|
898
|
+
# still makes the run exit non-zero).
|
|
899
|
+
database_available: bool | None = None
|
|
883
900
|
|
|
884
901
|
# -- bookkeeping --------------------------------------------------------
|
|
885
902
|
|
|
@@ -1436,6 +1453,7 @@ class TestSuite:
|
|
|
1436
1453
|
return
|
|
1437
1454
|
self.record("live prerequisites", "live", True, 0.0)
|
|
1438
1455
|
|
|
1456
|
+
self.check_database_connectivity()
|
|
1439
1457
|
self.check_live_hygiene()
|
|
1440
1458
|
if self.select("wheel install smoke"):
|
|
1441
1459
|
self.run_wheel_install_smoke()
|
|
@@ -1445,6 +1463,110 @@ class TestSuite:
|
|
|
1445
1463
|
self.run_live_permission_cycles(environment)
|
|
1446
1464
|
self.check_live_hygiene()
|
|
1447
1465
|
|
|
1466
|
+
def check_database_connectivity(self) -> None:
|
|
1467
|
+
"""Probe the pgsql pod once, up front, so an expired AWS SSO session
|
|
1468
|
+
is reported as a single early failure instead of crashing a later
|
|
1469
|
+
DB-backed test mid-run.
|
|
1470
|
+
|
|
1471
|
+
Only DB-backed live tests need the pod (via `kubectl exec` against
|
|
1472
|
+
EKS, which requires a valid AWS SSO session), so the probe is skipped
|
|
1473
|
+
when none of them are selected. When the probe fails on an interactive
|
|
1474
|
+
run, the harness refreshes AWS SSO and re-probes (see
|
|
1475
|
+
recover_database_access). On unrecovered failure, DB-backed tests skip
|
|
1476
|
+
themselves and the run still exits non-zero via this recorded failure.
|
|
1477
|
+
"""
|
|
1478
|
+
label = "live: database connectivity"
|
|
1479
|
+
if not self.test_selected(label, *DATABASE_BACKED_LIVE_CHECKS):
|
|
1480
|
+
return
|
|
1481
|
+
started = time.monotonic()
|
|
1482
|
+
error = self.probe_database()
|
|
1483
|
+
if error is not None and sys.stdin.isatty():
|
|
1484
|
+
error = self.recover_database_access(error)
|
|
1485
|
+
if error is None:
|
|
1486
|
+
self.database_available = True
|
|
1487
|
+
self.record(label, "live", True, time.monotonic() - started)
|
|
1488
|
+
else:
|
|
1489
|
+
self.database_available = False
|
|
1490
|
+
self.record(
|
|
1491
|
+
label, "live", False, time.monotonic() - started, database_error_detail(error)
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
def probe_database(self) -> str | None:
|
|
1495
|
+
"""Run `SELECT 1` on the pgsql pod; return None on success, else the error."""
|
|
1496
|
+
from tests import setup as instance_setup
|
|
1497
|
+
|
|
1498
|
+
try:
|
|
1499
|
+
kubectl_config = cast("dict[str, Any]", load_setup_config()["kubectl"])
|
|
1500
|
+
instance_setup.run_sql(kubectl_config, "SELECT 1;", timeout_seconds=30)
|
|
1501
|
+
except Exception as exception:
|
|
1502
|
+
return str(exception)
|
|
1503
|
+
return None
|
|
1504
|
+
|
|
1505
|
+
def recover_database_access(self, error: str) -> str | None:
|
|
1506
|
+
"""Refresh AWS SSO interactively, then re-probe until the pod responds.
|
|
1507
|
+
|
|
1508
|
+
Runs `aws sso login` once, then retries the probe every 10s for up to
|
|
1509
|
+
10 minutes. Returns None once the pod is reachable, or the latest
|
|
1510
|
+
error if SSO login could not run or the pod stayed unreachable.
|
|
1511
|
+
"""
|
|
1512
|
+
log.warning("Database probe failed: %s", database_error_detail(error))
|
|
1513
|
+
if not self.run_aws_sso_login():
|
|
1514
|
+
return error
|
|
1515
|
+
deadline = time.monotonic() + DATABASE_PROBE_RETRY_TIMEOUT_SECONDS
|
|
1516
|
+
while True:
|
|
1517
|
+
probe_error = self.probe_database()
|
|
1518
|
+
if probe_error is None:
|
|
1519
|
+
log.info("Database reachable after AWS SSO refresh.")
|
|
1520
|
+
return None
|
|
1521
|
+
if time.monotonic() >= deadline:
|
|
1522
|
+
return probe_error
|
|
1523
|
+
log.info(
|
|
1524
|
+
"Database still unreachable; retrying in %ds (up to 10 min total)...",
|
|
1525
|
+
DATABASE_PROBE_RETRY_INTERVAL_SECONDS,
|
|
1526
|
+
)
|
|
1527
|
+
time.sleep(DATABASE_PROBE_RETRY_INTERVAL_SECONDS)
|
|
1528
|
+
|
|
1529
|
+
def resolve_aws_profile(self) -> str | None:
|
|
1530
|
+
"""AWS profile for SSO login: AWS_PROFILE, then setup.yaml
|
|
1531
|
+
kubectl.awsProfile, else None (the default profile)."""
|
|
1532
|
+
profile = os.environ.get("AWS_PROFILE")
|
|
1533
|
+
if profile:
|
|
1534
|
+
return profile
|
|
1535
|
+
kubectl_config = cast("dict[str, Any]", load_setup_config().get("kubectl") or {})
|
|
1536
|
+
configured = kubectl_config.get("awsProfile")
|
|
1537
|
+
return str(configured) if configured else None
|
|
1538
|
+
|
|
1539
|
+
def run_aws_sso_login(self) -> bool:
|
|
1540
|
+
"""Run `aws sso login` for the resolved profile; return True if it ran ok."""
|
|
1541
|
+
command = ["aws", "sso", "login"]
|
|
1542
|
+
profile = self.resolve_aws_profile()
|
|
1543
|
+
if profile:
|
|
1544
|
+
command += ["--profile", profile]
|
|
1545
|
+
log.warning("Running `%s` - complete the browser prompt to continue...", " ".join(command))
|
|
1546
|
+
try:
|
|
1547
|
+
completed = subprocess.run(command, timeout=AWS_SSO_LOGIN_TIMEOUT_SECONDS, check=False)
|
|
1548
|
+
except FileNotFoundError:
|
|
1549
|
+
log.error("aws CLI not found; cannot refresh AWS SSO automatically")
|
|
1550
|
+
return False
|
|
1551
|
+
except subprocess.TimeoutExpired:
|
|
1552
|
+
log.error("`aws sso login` timed out after %ds", AWS_SSO_LOGIN_TIMEOUT_SECONDS)
|
|
1553
|
+
return False
|
|
1554
|
+
if completed.returncode != 0:
|
|
1555
|
+
log.error("`aws sso login` failed (exit %d)", completed.returncode)
|
|
1556
|
+
return False
|
|
1557
|
+
return True
|
|
1558
|
+
|
|
1559
|
+
def skip_if_database_unavailable(self, label: str) -> bool:
|
|
1560
|
+
"""Log and return True when a DB-backed test must skip the pgsql pod."""
|
|
1561
|
+
if self.database_available is False:
|
|
1562
|
+
log.warning(
|
|
1563
|
+
"SKIP [live] %s - database unavailable "
|
|
1564
|
+
"(see 'live: database connectivity'); run `aws sso login`",
|
|
1565
|
+
label,
|
|
1566
|
+
)
|
|
1567
|
+
return True
|
|
1568
|
+
return False
|
|
1569
|
+
|
|
1448
1570
|
def check_live_hygiene(self) -> None:
|
|
1449
1571
|
"""Cheap small-state guard: no pending bindIDs should ever persist.
|
|
1450
1572
|
|
|
@@ -2178,6 +2300,8 @@ class TestSuite:
|
|
|
2178
2300
|
label = "live: perms follow saml group change"
|
|
2179
2301
|
if not self.select(label):
|
|
2180
2302
|
return
|
|
2303
|
+
if self.skip_if_database_unavailable(label):
|
|
2304
|
+
return
|
|
2181
2305
|
log.info("\n--- Live: permissions follow a SAML group change ---")
|
|
2182
2306
|
import yaml
|
|
2183
2307
|
|
|
@@ -2428,6 +2552,32 @@ class TestSuite:
|
|
|
2428
2552
|
"; ".join(mismatches) if mismatches else f"{len(expected_pending)} repo(s) match",
|
|
2429
2553
|
)
|
|
2430
2554
|
|
|
2555
|
+
def live_maps_path(self) -> Path:
|
|
2556
|
+
"""Endpoint root maps.yaml the permission cycles apply (CLI default)."""
|
|
2557
|
+
from src_auth_perms_sync.shared import backups
|
|
2558
|
+
|
|
2559
|
+
return (
|
|
2560
|
+
Path(backups.ARTIFACTS_DIR_NAME)
|
|
2561
|
+
/ backups.endpoint_directory_name(self.endpoint)
|
|
2562
|
+
/ backups.DEFAULT_MAPS_FILE_NAME
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2565
|
+
def ensure_live_maps_seed(self) -> None:
|
|
2566
|
+
"""Seed a valid maps.yaml so a fresh worktree can run the cycles.
|
|
2567
|
+
|
|
2568
|
+
The permission cycles apply the endpoint root maps.yaml. On a fresh
|
|
2569
|
+
worktree that file does not exist, so the baseline `get` writes the
|
|
2570
|
+
empty default template, which then fails `set` validation. Seed the
|
|
2571
|
+
synthetic-corpus maps when the file is missing or unusable; leave an
|
|
2572
|
+
operator-maintained maps.yaml with real rules untouched.
|
|
2573
|
+
"""
|
|
2574
|
+
maps_path = self.live_maps_path()
|
|
2575
|
+
if maps_file_has_usable_rule(maps_path):
|
|
2576
|
+
return
|
|
2577
|
+
maps_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2578
|
+
maps_path.write_text(LIVE_MAPS_SEED_PATH.read_text(encoding="utf-8"), encoding="utf-8")
|
|
2579
|
+
log.info("Seeded live maps file from %s -> %s", LIVE_MAPS_SEED_PATH, maps_path)
|
|
2580
|
+
|
|
2431
2581
|
def run_live_permission_cycles(self, environment: dict[str, str]) -> None:
|
|
2432
2582
|
# The baseline get is a prerequisite for both cycles, so it runs when
|
|
2433
2583
|
# any of them is selected.
|
|
@@ -2445,6 +2595,7 @@ class TestSuite:
|
|
|
2445
2595
|
if not want_baseline:
|
|
2446
2596
|
return
|
|
2447
2597
|
log.info("\n--- Live: permission cycles with independent read-back ---")
|
|
2598
|
+
self.ensure_live_maps_seed()
|
|
2448
2599
|
baseline = self.run_cli_case(
|
|
2449
2600
|
CliCase(
|
|
2450
2601
|
"live: get user baseline",
|
|
@@ -3557,6 +3708,33 @@ def load_setup_config() -> dict[str, Any]:
|
|
|
3557
3708
|
return cast("dict[str, Any]", yaml.safe_load(SETUP_CONFIG_PATH.read_text(encoding="utf-8")))
|
|
3558
3709
|
|
|
3559
3710
|
|
|
3711
|
+
def database_error_detail(error: str) -> str:
|
|
3712
|
+
"""Summarize a pgsql/kubectl failure, calling out expired AWS SSO."""
|
|
3713
|
+
lowered = error.lower()
|
|
3714
|
+
if "sso session" in lowered and ("expired" in lowered or "invalid" in lowered):
|
|
3715
|
+
return "pgsql pod unreachable: AWS SSO session expired - run `aws sso login`"
|
|
3716
|
+
first_line = next((line for line in error.splitlines() if line.strip()), "unknown error")
|
|
3717
|
+
return f"pgsql pod unreachable: {first_line.strip()}"
|
|
3718
|
+
|
|
3719
|
+
|
|
3720
|
+
def maps_file_has_usable_rule(path: Path) -> bool:
|
|
3721
|
+
"""Return True if the maps file has a rule with both users and repos.
|
|
3722
|
+
|
|
3723
|
+
A `set` run needs at least one rule carrying both sections; the empty
|
|
3724
|
+
default template (a lone `- name: Map 1`) fails validation, so it is not
|
|
3725
|
+
usable.
|
|
3726
|
+
"""
|
|
3727
|
+
import yaml
|
|
3728
|
+
|
|
3729
|
+
if not path.is_file():
|
|
3730
|
+
return False
|
|
3731
|
+
loaded = cast("dict[str, Any]", yaml.safe_load(path.read_text(encoding="utf-8")) or {})
|
|
3732
|
+
for rule in cast("list[dict[str, Any]]", loaded.get("maps") or []):
|
|
3733
|
+
if rule.get("users") and rule.get("repos"):
|
|
3734
|
+
return True
|
|
3735
|
+
return False
|
|
3736
|
+
|
|
3737
|
+
|
|
3560
3738
|
def fixture_grants(case_name: str, file_name: str) -> dict[str, set[str]] | None:
|
|
3561
3739
|
"""Return {repo name: usernames} from one fixture state file."""
|
|
3562
3740
|
path = FIXTURES_DIR / case_name / file_name
|
|
@@ -11,6 +11,10 @@ kubectl:
|
|
|
11
11
|
databaseUser: sg
|
|
12
12
|
database: sg
|
|
13
13
|
tenantID: 1
|
|
14
|
+
# Optional. AWS SSO profile the live harness refreshes when the pgsql pod
|
|
15
|
+
# is unreachable on an interactive run. Precedence: AWS_PROFILE env var,
|
|
16
|
+
# then this key, then the default profile. Omit to use the default.
|
|
17
|
+
# awsProfile: my-sso-profile
|
|
14
18
|
|
|
15
19
|
users:
|
|
16
20
|
# Synthetic users are pre-provisioned in bulk; setup verifies the count
|
|
@@ -5,6 +5,7 @@ import io
|
|
|
5
5
|
import os
|
|
6
6
|
import tempfile
|
|
7
7
|
import unittest
|
|
8
|
+
from collections.abc import Sequence
|
|
8
9
|
from concurrent.futures import ThreadPoolExecutor
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from types import SimpleNamespace
|
|
@@ -429,6 +430,97 @@ class CliConfigTests(unittest.TestCase):
|
|
|
429
430
|
self.assertIn("--artifacts-dir", help_text)
|
|
430
431
|
self.assertIn("--no-files", help_text)
|
|
431
432
|
|
|
433
|
+
def test_set_help_lists_set_specific_options(self) -> None:
|
|
434
|
+
help_text = self.load_cli_help_text("set", "--help")
|
|
435
|
+
|
|
436
|
+
self.assertIn("--full", help_text)
|
|
437
|
+
self.assertIn("--maps-path", help_text)
|
|
438
|
+
self.assertIn("--apply", help_text)
|
|
439
|
+
|
|
440
|
+
def test_restore_help_lists_restore_specific_options(self) -> None:
|
|
441
|
+
help_text = self.load_cli_help_text("restore", "--help")
|
|
442
|
+
|
|
443
|
+
self.assertIn("--restore-path", help_text)
|
|
444
|
+
self.assertIn("--apply", help_text)
|
|
445
|
+
self.assertNotIn("--maps-path", help_text)
|
|
446
|
+
|
|
447
|
+
def test_sync_saml_orgs_help_lists_mode_options(self) -> None:
|
|
448
|
+
help_text = self.load_cli_help_text("sync-saml-orgs", "--help")
|
|
449
|
+
|
|
450
|
+
self.assertIn("--full", help_text)
|
|
451
|
+
self.assertIn("--users", help_text)
|
|
452
|
+
self.assertNotIn("--maps-path", help_text)
|
|
453
|
+
|
|
454
|
+
def test_version_flag_reports_package_version(self) -> None:
|
|
455
|
+
captured_stdout = io.StringIO()
|
|
456
|
+
|
|
457
|
+
with (
|
|
458
|
+
contextlib.redirect_stdout(captured_stdout),
|
|
459
|
+
self.assertRaises(SystemExit) as exit_context,
|
|
460
|
+
):
|
|
461
|
+
cli.load_cli(["--version"])
|
|
462
|
+
|
|
463
|
+
self.assertEqual(0, exit_context.exception.code)
|
|
464
|
+
self.assertIn("src-auth-perms-sync", captured_stdout.getvalue())
|
|
465
|
+
|
|
466
|
+
def test_parallelism_config_is_loaded_from_env(self) -> None:
|
|
467
|
+
config = load_config_from_env(SRC_AUTH_PERMS_SYNC_PARALLELISM="8")
|
|
468
|
+
|
|
469
|
+
self.assertEqual(8, config.parallelism)
|
|
470
|
+
|
|
471
|
+
def test_parallelism_rejects_values_below_one(self) -> None:
|
|
472
|
+
with self.assertRaisesRegex(shared_config.ConfigError, "greater than or equal to 1"):
|
|
473
|
+
load_config_from_env(SRC_AUTH_PERMS_SYNC_PARALLELISM="0")
|
|
474
|
+
|
|
475
|
+
def test_max_attempts_config_is_loaded_from_env(self) -> None:
|
|
476
|
+
config = load_config_from_env(SRC_AUTH_PERMS_SYNC_MAX_ATTEMPTS="3")
|
|
477
|
+
|
|
478
|
+
self.assertEqual(3, config.max_attempts)
|
|
479
|
+
|
|
480
|
+
def test_max_attempts_rejects_values_below_one(self) -> None:
|
|
481
|
+
with self.assertRaisesRegex(shared_config.ConfigError, "greater than or equal to 1"):
|
|
482
|
+
load_config_from_env(SRC_AUTH_PERMS_SYNC_MAX_ATTEMPTS="0")
|
|
483
|
+
|
|
484
|
+
def test_sample_interval_config_is_loaded_from_env(self) -> None:
|
|
485
|
+
config = load_config_from_env(SRC_AUTH_PERMS_SYNC_SAMPLE_INTERVAL="2.5")
|
|
486
|
+
|
|
487
|
+
self.assertEqual(2.5, config.sample_interval)
|
|
488
|
+
|
|
489
|
+
def test_sample_interval_allows_zero_to_disable_sampling(self) -> None:
|
|
490
|
+
config = load_config_from_env(SRC_AUTH_PERMS_SYNC_SAMPLE_INTERVAL="0")
|
|
491
|
+
|
|
492
|
+
self.assertEqual(0, config.sample_interval)
|
|
493
|
+
|
|
494
|
+
def test_sample_interval_rejects_negative_values(self) -> None:
|
|
495
|
+
with self.assertRaisesRegex(shared_config.ConfigError, "greater than or equal to 0"):
|
|
496
|
+
load_config_from_env(SRC_AUTH_PERMS_SYNC_SAMPLE_INTERVAL="-5")
|
|
497
|
+
|
|
498
|
+
def test_users_config_drops_empty_and_whitespace_only_entries(self) -> None:
|
|
499
|
+
config = load_config_from_env(SRC_AUTH_PERMS_SYNC_USERS=" , ,, ")
|
|
500
|
+
|
|
501
|
+
self.assertEqual((), config.users)
|
|
502
|
+
|
|
503
|
+
def test_load_cli_rejects_restore_path_env_var_on_set(self) -> None:
|
|
504
|
+
self.assert_load_cli_rejects_env(
|
|
505
|
+
["set", "--full"],
|
|
506
|
+
{"SRC_AUTH_PERMS_SYNC_RESTORE_PATH": "snapshot.json"},
|
|
507
|
+
"--restore-path requires the restore command",
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
def test_load_cli_rejects_maps_path_env_var_on_restore(self) -> None:
|
|
511
|
+
self.assert_load_cli_rejects_env(
|
|
512
|
+
["restore", "--restore-path", "snapshot.json"],
|
|
513
|
+
{"SRC_AUTH_PERMS_SYNC_MAPS_PATH": "maps.yaml"},
|
|
514
|
+
"--maps-path requires the get or set command",
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def test_load_cli_rejects_full_env_var_on_get(self) -> None:
|
|
518
|
+
self.assert_load_cli_rejects_env(
|
|
519
|
+
["get"],
|
|
520
|
+
{"SRC_AUTH_PERMS_SYNC_FULL": "true"},
|
|
521
|
+
"--full requires the set or sync-saml-orgs command",
|
|
522
|
+
)
|
|
523
|
+
|
|
432
524
|
def test_validate_config_rejects_restore_without_restore_path(self) -> None:
|
|
433
525
|
self.assert_config_error("restore", make_config(), "restore requires --restore-path")
|
|
434
526
|
|
|
@@ -953,3 +1045,42 @@ class CliConfigTests(unittest.TestCase):
|
|
|
953
1045
|
cli.validate_config(command_name, config)
|
|
954
1046
|
self.assertEqual(2, exit_context.exception.code)
|
|
955
1047
|
self.assertIn(expected_message, captured_stderr.getvalue())
|
|
1048
|
+
|
|
1049
|
+
def load_cli_help_text(self, *argv: str) -> str:
|
|
1050
|
+
captured_stdout = io.StringIO()
|
|
1051
|
+
with (
|
|
1052
|
+
contextlib.redirect_stdout(captured_stdout),
|
|
1053
|
+
self.assertRaises(SystemExit) as exit_context,
|
|
1054
|
+
):
|
|
1055
|
+
cli.load_cli([*argv])
|
|
1056
|
+
self.assertEqual(0, exit_context.exception.code)
|
|
1057
|
+
return captured_stdout.getvalue()
|
|
1058
|
+
|
|
1059
|
+
def assert_load_cli_rejects_env(
|
|
1060
|
+
self,
|
|
1061
|
+
argv: Sequence[str],
|
|
1062
|
+
env: dict[str, str],
|
|
1063
|
+
expected_message: str,
|
|
1064
|
+
) -> None:
|
|
1065
|
+
with (
|
|
1066
|
+
tempfile.TemporaryDirectory() as directory,
|
|
1067
|
+
mock.patch.dict(
|
|
1068
|
+
os.environ,
|
|
1069
|
+
{
|
|
1070
|
+
"SRC_ENDPOINT": "https://sourcegraph.example.com",
|
|
1071
|
+
"SRC_ACCESS_TOKEN": "secret",
|
|
1072
|
+
**env,
|
|
1073
|
+
},
|
|
1074
|
+
clear=True,
|
|
1075
|
+
),
|
|
1076
|
+
):
|
|
1077
|
+
env_file = Path(directory) / ".env"
|
|
1078
|
+
env_file.write_text("")
|
|
1079
|
+
captured_stderr = io.StringIO()
|
|
1080
|
+
with (
|
|
1081
|
+
contextlib.redirect_stderr(captured_stderr),
|
|
1082
|
+
self.assertRaises(SystemExit) as exit_context,
|
|
1083
|
+
):
|
|
1084
|
+
cli.load_cli([*argv, "--env-file", str(env_file)])
|
|
1085
|
+
self.assertEqual(2, exit_context.exception.code)
|
|
1086
|
+
self.assertIn(expected_message, captured_stderr.getvalue())
|
|
@@ -548,7 +548,7 @@ dev = [
|
|
|
548
548
|
requires-dist = [
|
|
549
549
|
{ name = "json5", specifier = ">=0.15.0" },
|
|
550
550
|
{ name = "pyyaml", specifier = ">=6.0.3" },
|
|
551
|
-
{ name = "src-py-lib", extras = ["otel"], specifier = "==0.3.
|
|
551
|
+
{ name = "src-py-lib", extras = ["otel"], specifier = "==0.3.2" },
|
|
552
552
|
]
|
|
553
553
|
|
|
554
554
|
[package.metadata.requires-dev]
|
|
@@ -560,7 +560,7 @@ dev = [
|
|
|
560
560
|
|
|
561
561
|
[[package]]
|
|
562
562
|
name = "src-py-lib"
|
|
563
|
-
version = "0.3.
|
|
563
|
+
version = "0.3.2"
|
|
564
564
|
source = { registry = "https://pypi.org/simple" }
|
|
565
565
|
dependencies = [
|
|
566
566
|
{ name = "httpx" },
|
|
@@ -568,9 +568,9 @@ dependencies = [
|
|
|
568
568
|
{ name = "pydantic" },
|
|
569
569
|
{ name = "python-dotenv" },
|
|
570
570
|
]
|
|
571
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
571
|
+
sdist = { url = "https://files.pythonhosted.org/packages/34/b5/e711949b9c27b3cbadfcc90cf56c43db308b319fd0732f12c0a4722bc9f1/src_py_lib-0.3.2.tar.gz", hash = "sha256:5675f2ee4eb7cde8daca066550f9bf684c94e76b3496618261794dcb765e85bc", size = 97377, upload-time = "2026-06-27T19:20:46.334Z" }
|
|
572
572
|
wheels = [
|
|
573
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
573
|
+
{ url = "https://files.pythonhosted.org/packages/38/1e/a41a634803bb330ba7cfdaa2d2757ae129e3d66bf35f90e1796663edfdc0/src_py_lib-0.3.2-py3-none-any.whl", hash = "sha256:9cd81d2a98d32137fc6e799a5b4decd61b72f836b5d049b21a70247dac0f7bd5", size = 54434, upload-time = "2026-06-27T19:20:44.937Z" },
|
|
574
574
|
]
|
|
575
575
|
|
|
576
576
|
[package.optional-dependencies]
|