src-auth-perms-sync 0.5.0__tar.gz → 0.6.0__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.5.0 → src_auth_perms_sync-0.6.0}/PKG-INFO +15 -1
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/README.md +14 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/PLAN.md +6 -6
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/TODO.md +0 -10
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/__init__.py +2 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/cli.py +61 -9
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/command.py +20 -5
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/full_set.py +3 -2
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/maps.py +22 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/workflow.py +16 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/case_runner.py +27 -4
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/test_local_cases.py +84 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_cli_config.py +5 -0
- src_auth_perms_sync-0.6.0/tests/unit/test_mapping_rules.py +116 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_module_api.py +57 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.env.example +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.github/CODEOWNERS +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.github/workflows/ci.yml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.github/workflows/release.yml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.github/workflows/validate.yml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.gitignore +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.markdownlint-cli2.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/.python-version +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/AGENTS.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/LICENSE +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/SECURITY.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/audit-dead-code.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/engineering-requests.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/hooks/pre-commit +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/memory-analysis/mapping-efficiency.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/memory-analysis/memory-efficiency-analyze.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/memory-analysis/memory-efficiency-generate.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/memory-analysis/memory-efficiency-monitor-sourcegraph.sh +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/memory-analysis/memory-efficiency.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/dev/update-python-versions.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/maps-example.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/pyproject.toml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/renovate.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/__main__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/orgs/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/orgs/command.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/orgs/queries.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/orgs/sync.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/orgs/types.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/apply.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/mapping.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/queries.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/restore.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/snapshot.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/sourcegraph.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/types.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/backups.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/queries.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/run_context.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/saml_groups.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/site_config.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/sourcegraph.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/shared/types.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/README.md +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-by-email-and-list/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-by-email-and-list/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-by-email-and-list/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-preserves-existing/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-preserves-existing/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/add-users-preserves-existing/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/and-filters-intersect/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/and-filters-intersect/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/and-filters-intersect/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/empty-maps-noop/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/empty-maps-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-dry-run/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-preserves-pending/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-preserves-pending/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-preserves-pending/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-removes-stale-grant/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-unions/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-unions/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-unions/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-with-backup/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-with-backup/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/full-overwrite-with-backup/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/get-full-snapshot/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/get-repos-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/get-user-grants/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-bad-regex/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-bad-regex/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-missing-repos-section/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-missing-repos-section/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-restore-wrong-schema-version/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-restore-wrong-schema-version/snapshot.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-created-after-date/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-created-after-date/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-repos-created-after-date/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-repos-created-after-date/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-unknown-repo/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-unknown-repo/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-unknown-user/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-set-unknown-user/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-unknown-selector-field/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/invalid-unknown-selector-field/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/match-provider-and-host-fields/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/match-provider-and-host-fields/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/match-provider-and-host-fields/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/no-match-noop/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/no-match-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/regex-filters-scope/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/regex-filters-scope/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/regex-filters-scope/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-applies-snapshot/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-applies-snapshot/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-applies-snapshot/snapshot.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-dry-run-noop/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-dry-run-noop/snapshot.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-missing-file/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-restores-pending/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-restores-pending/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/restore-restores-pending/snapshot.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-filter/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-filter/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-filter/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-live/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-live/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/saml-group-live/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-created-after-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-created-after-temp-user/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-created-after-temp-user/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-created-after-temp-user/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-full-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after-noop/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-created-after-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-filter/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-filter/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-filter/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-without-explicit-perms/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-without-explicit-perms/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-repos-without-perms-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-created-after/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-created-after/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-created-after/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-created-after-noop/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-created-after-noop/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/after.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/before.json +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-explicit-perms-default-batch/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/e2e/fixtures/set-users-without-perms-sync-saml-orgs-dry-run/maps.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/integration/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/integration/test_cli_entrypoint.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/run.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/setup.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/setup.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/tests.yaml +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/__init__.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_apply.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_backups.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_command_additive.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_command_files.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_maps.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_permissions_sourcegraph.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_restore.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_saml_groups.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/tests/unit/test_snapshot.py +0 -0
- {src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: src-auth-perms-sync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -170,6 +170,20 @@ for provider in get_result.auth_providers:
|
|
|
170
170
|
for code_host in get_result.code_hosts:
|
|
171
171
|
...
|
|
172
172
|
|
|
173
|
+
# Mapping rules can be passed in memory instead of a maps YAML file —
|
|
174
|
+
# same structure and validation as maps.yaml entries:
|
|
175
|
+
rules: list[src.MappingRule] = [
|
|
176
|
+
{
|
|
177
|
+
"name": "Map 1",
|
|
178
|
+
"users": {"usernameRegexes": [".*"]},
|
|
179
|
+
"repos": {"codeHostConnection": {"kind": "GITHUB"}},
|
|
180
|
+
},
|
|
181
|
+
]
|
|
182
|
+
result = src.Set(config, mapping_rules=rules)
|
|
183
|
+
# When files are enabled, the rules actually used are written into the
|
|
184
|
+
# run directory for auditability. Snapshots still gate apply=True unless
|
|
185
|
+
# no_files=True and no_backup=True are both set explicitly.
|
|
186
|
+
|
|
173
187
|
# Other command wrappers:
|
|
174
188
|
# result = src.Restore(config)
|
|
175
189
|
# result = src.SyncSamlOrgs(config)
|
|
@@ -147,6 +147,20 @@ for provider in get_result.auth_providers:
|
|
|
147
147
|
for code_host in get_result.code_hosts:
|
|
148
148
|
...
|
|
149
149
|
|
|
150
|
+
# Mapping rules can be passed in memory instead of a maps YAML file —
|
|
151
|
+
# same structure and validation as maps.yaml entries:
|
|
152
|
+
rules: list[src.MappingRule] = [
|
|
153
|
+
{
|
|
154
|
+
"name": "Map 1",
|
|
155
|
+
"users": {"usernameRegexes": [".*"]},
|
|
156
|
+
"repos": {"codeHostConnection": {"kind": "GITHUB"}},
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
result = src.Set(config, mapping_rules=rules)
|
|
160
|
+
# When files are enabled, the rules actually used are written into the
|
|
161
|
+
# run directory for auditability. Snapshots still gate apply=True unless
|
|
162
|
+
# no_files=True and no_backup=True are both set explicitly.
|
|
163
|
+
|
|
150
164
|
# Other command wrappers:
|
|
151
165
|
# result = src.Restore(config)
|
|
152
166
|
# result = src.SyncSamlOrgs(config)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# PLAN: file handling + observability redesign
|
|
2
2
|
|
|
3
|
-
> **Status (2026-06-12): implemented**,
|
|
4
|
-
> (in-memory mapping rules for `Set`)
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
>
|
|
3
|
+
> **Status (2026-06-12): fully implemented**, including Track A Phase A4
|
|
4
|
+
> (in-memory mapping rules for `Set`). Shipped as src-py-lib v0.3.0 and
|
|
5
|
+
> src-auth-perms-sync v0.5.0 (`refactor-logging-and-files` PR), with A4
|
|
6
|
+
> following in the `in-memory-mapping-rules` PR. Phases were compressed
|
|
7
|
+
> into one PR per repo (no ContextVar bridge phase was needed);
|
|
8
|
+
> everything landed as specified.
|
|
9
9
|
|
|
10
10
|
Spans both repos (we own both, both greenfield, no external users):
|
|
11
11
|
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
-
## Follow-up: in-memory mapping rules for Set (PLAN.md Track A Phase A4)
|
|
4
|
-
|
|
5
|
-
The rest of [PLAN.md](./PLAN.md) is implemented (src-py-lib v0.3.0 +
|
|
6
|
-
the consumer refactor-logging-and-files PR). The one deliberately
|
|
7
|
-
deferred piece, marked optional in the plan: let module callers pass
|
|
8
|
-
parsed mapping rules to `Set` instead of a maps file, so the full
|
|
9
|
-
get → assemble → dry-run loop never touches disk. Snapshots must stay
|
|
10
|
-
on disk for `--apply` (reversibility invariant); `no_files` + `apply`
|
|
11
|
-
must keep requiring `no_backup`.
|
|
12
|
-
|
|
13
3
|
## High priority: Remote trigger on demand
|
|
14
4
|
|
|
15
5
|
- Sourcegraph webhook for new user coming in v7.4.0
|
|
@@ -16,6 +16,7 @@ from src_py_lib import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from .cli import CommandResult, Config, Get, GetResult, Restore, Set, SyncSamlOrgs
|
|
19
|
+
from .permissions.types import MappingRule
|
|
19
20
|
from .shared.backups import RunPaths
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
|
28
29
|
"GetResult",
|
|
29
30
|
"InMemoryEventSink",
|
|
30
31
|
"JSONLEventSink",
|
|
32
|
+
"MappingRule",
|
|
31
33
|
"NullEventSink",
|
|
32
34
|
"Restore",
|
|
33
35
|
"RunPaths",
|
|
@@ -26,6 +26,7 @@ from src_py_lib.utils import config as config_utils
|
|
|
26
26
|
|
|
27
27
|
from .orgs import command as organizations_command
|
|
28
28
|
from .permissions import command as permissions_command
|
|
29
|
+
from .permissions import mapping as permissions_mapping
|
|
29
30
|
from .permissions import maps as permissions_maps
|
|
30
31
|
from .permissions import types as permission_types
|
|
31
32
|
from .shared import backups, run_context, site_config
|
|
@@ -741,6 +742,7 @@ def run_with_client(
|
|
|
741
742
|
endpoint: str,
|
|
742
743
|
run_paths: backups.RunPaths,
|
|
743
744
|
worker_pool: ThreadPoolExecutor,
|
|
745
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
744
746
|
) -> run_context.CommandData:
|
|
745
747
|
"""Create a client, run the selected command, and always close HTTP resources."""
|
|
746
748
|
http = src.HTTPClient(
|
|
@@ -756,7 +758,7 @@ def run_with_client(
|
|
|
756
758
|
fetch_sg_traces=config.fetch_sg_traces,
|
|
757
759
|
)
|
|
758
760
|
try:
|
|
759
|
-
return run_command(config, command, client, run_paths, worker_pool)
|
|
761
|
+
return run_command(config, command, client, run_paths, worker_pool, mapping_rules)
|
|
760
762
|
finally:
|
|
761
763
|
client.http.close()
|
|
762
764
|
|
|
@@ -767,6 +769,7 @@ def run_command(
|
|
|
767
769
|
client: src.SourcegraphClient,
|
|
768
770
|
run_paths: backups.RunPaths,
|
|
769
771
|
worker_pool: ThreadPoolExecutor,
|
|
772
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
770
773
|
) -> run_context.CommandData:
|
|
771
774
|
"""Dispatch the selected command."""
|
|
772
775
|
sourcegraph_site_config = site_config.validate_site_config(client)
|
|
@@ -775,7 +778,13 @@ def run_command(
|
|
|
775
778
|
command_data = run_get(config, client, sourcegraph_site_config, run_paths, worker_pool)
|
|
776
779
|
elif command.name == "set":
|
|
777
780
|
command_data = run_set(
|
|
778
|
-
config,
|
|
781
|
+
config,
|
|
782
|
+
command,
|
|
783
|
+
client,
|
|
784
|
+
sourcegraph_site_config,
|
|
785
|
+
run_paths,
|
|
786
|
+
worker_pool,
|
|
787
|
+
mapping_rules=mapping_rules,
|
|
779
788
|
)
|
|
780
789
|
elif command.name == "restore":
|
|
781
790
|
run_restore(config, client, sourcegraph_site_config, run_paths, worker_pool)
|
|
@@ -810,10 +819,22 @@ def run_set(
|
|
|
810
819
|
sourcegraph_site_config: site_config.SiteConfig,
|
|
811
820
|
run_paths: backups.RunPaths,
|
|
812
821
|
worker_pool: ThreadPoolExecutor,
|
|
822
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
813
823
|
) -> run_context.CommandData:
|
|
814
|
-
"""Run the selected repo-permission sync command.
|
|
824
|
+
"""Run the selected repo-permission sync command.
|
|
825
|
+
|
|
826
|
+
With in-memory `mapping_rules` (module callers), no maps file is read;
|
|
827
|
+
when files are enabled, the rules actually used are written into the run
|
|
828
|
+
directory so the audit trail stays faithful.
|
|
829
|
+
"""
|
|
815
830
|
assert command.set_options is not None
|
|
816
|
-
|
|
831
|
+
if mapping_rules is None:
|
|
832
|
+
require_set_input_file(run_paths.maps_path)
|
|
833
|
+
elif run_paths.write_files:
|
|
834
|
+
materialized_maps_path = run_paths.input_copy_path(backups.DEFAULT_MAPS_FILE_NAME)
|
|
835
|
+
permissions_maps.dump_mapping_rules_yaml(materialized_maps_path, mapping_rules)
|
|
836
|
+
run_paths = dataclasses.replace(run_paths, maps_path=materialized_maps_path)
|
|
837
|
+
log.info("Wrote in-memory mapping rules for audit: %s", materialized_maps_path)
|
|
817
838
|
return permissions_command.cmd_set(
|
|
818
839
|
client,
|
|
819
840
|
run_paths,
|
|
@@ -828,6 +849,7 @@ def run_set(
|
|
|
828
849
|
do_backup=not config.no_backup,
|
|
829
850
|
retain_saml_group_users=command.sync_saml_organizations,
|
|
830
851
|
worker_pool=worker_pool,
|
|
852
|
+
mapping_rules=mapping_rules,
|
|
831
853
|
)
|
|
832
854
|
|
|
833
855
|
|
|
@@ -970,9 +992,22 @@ def Get(config: Config, *, event_sink: src.EventSink | None = None) -> GetResult
|
|
|
970
992
|
)
|
|
971
993
|
|
|
972
994
|
|
|
973
|
-
def Set(
|
|
974
|
-
|
|
975
|
-
|
|
995
|
+
def Set(
|
|
996
|
+
config: Config,
|
|
997
|
+
*,
|
|
998
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
999
|
+
event_sink: src.EventSink | None = None,
|
|
1000
|
+
) -> CommandResult:
|
|
1001
|
+
"""Run repository permission reconciliation.
|
|
1002
|
+
|
|
1003
|
+
`mapping_rules` lets module callers pass parsed rules directly (e.g.
|
|
1004
|
+
assembled from `GetResult` data) instead of a maps YAML file. The rules
|
|
1005
|
+
go through the same structural validation as file-loaded rules. When
|
|
1006
|
+
files are enabled, the rules actually used are written into the run
|
|
1007
|
+
directory for auditability; snapshots still gate `apply` unless
|
|
1008
|
+
`no_files` and `no_backup` are both set explicitly.
|
|
1009
|
+
"""
|
|
1010
|
+
succeeded, _, run_paths = _run("set", config, event_sink, mapping_rules=mapping_rules)
|
|
976
1011
|
return CommandResult(succeeded=succeeded, paths=run_paths)
|
|
977
1012
|
|
|
978
1013
|
|
|
@@ -992,11 +1027,21 @@ def _run(
|
|
|
992
1027
|
command_name: CommandName,
|
|
993
1028
|
config: Config,
|
|
994
1029
|
event_sink: src.EventSink | None,
|
|
1030
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
995
1031
|
) -> tuple[bool, run_context.CommandData | None, backups.RunPaths | None]:
|
|
996
1032
|
"""Run a module-mode command, reporting success instead of raising."""
|
|
997
1033
|
try:
|
|
998
|
-
command_data, run_paths = _run_or_raise(
|
|
1034
|
+
command_data, run_paths = _run_or_raise(
|
|
1035
|
+
command_name,
|
|
1036
|
+
config,
|
|
1037
|
+
event_sink=event_sink,
|
|
1038
|
+
mapping_rules=mapping_rules,
|
|
1039
|
+
)
|
|
999
1040
|
except SystemExit as exception:
|
|
1041
|
+
if isinstance(exception.code, str):
|
|
1042
|
+
# Module mode swallows the exit; surface the diagnostic through
|
|
1043
|
+
# the package logger so host applications can see why it failed.
|
|
1044
|
+
log.error("%s", exception.code)
|
|
1000
1045
|
return exception.code in (None, 0), None, None
|
|
1001
1046
|
except Exception:
|
|
1002
1047
|
log.exception("src-auth-perms-sync run failed.")
|
|
@@ -1035,6 +1080,7 @@ def _run_or_raise(
|
|
|
1035
1080
|
*,
|
|
1036
1081
|
cli_mode: bool = False,
|
|
1037
1082
|
event_sink: src.EventSink | None = None,
|
|
1083
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
1038
1084
|
) -> tuple[run_context.CommandData, backups.RunPaths]:
|
|
1039
1085
|
"""Run src-auth-perms-sync, preserving CLI-style exceptions.
|
|
1040
1086
|
|
|
@@ -1043,6 +1089,10 @@ def _run_or_raise(
|
|
|
1043
1089
|
application's logging configuration stays in charge.
|
|
1044
1090
|
"""
|
|
1045
1091
|
validate_config(command_name, config)
|
|
1092
|
+
if mapping_rules is not None:
|
|
1093
|
+
if command_name != "set":
|
|
1094
|
+
config_error("mapping_rules can only be passed to Set")
|
|
1095
|
+
permissions_mapping.validate_mapping_rules(mapping_rules)
|
|
1046
1096
|
command = resolve_command(command_name, config)
|
|
1047
1097
|
try:
|
|
1048
1098
|
endpoint = src.normalize_sourcegraph_endpoint(config.src_endpoint)
|
|
@@ -1102,7 +1152,9 @@ def _run_or_raise(
|
|
|
1102
1152
|
)
|
|
1103
1153
|
worker_pool = stack.enter_context(run_context.thread_pool(config.parallelism))
|
|
1104
1154
|
try:
|
|
1105
|
-
command_data = run_with_client(
|
|
1155
|
+
command_data = run_with_client(
|
|
1156
|
+
config, command, endpoint, run_paths, worker_pool, mapping_rules
|
|
1157
|
+
)
|
|
1106
1158
|
except SystemExit as exception:
|
|
1107
1159
|
reraise_system_exit_with_logged_error(exception)
|
|
1108
1160
|
return command_data, run_paths
|
|
@@ -26,11 +26,11 @@ from . import types as permission_types
|
|
|
26
26
|
from .workflow import (
|
|
27
27
|
load_discovery,
|
|
28
28
|
load_mapping_context_discovery,
|
|
29
|
-
load_mapping_rules,
|
|
30
29
|
load_repos_for_mapping_context,
|
|
31
30
|
load_repository_candidates_by_names,
|
|
32
31
|
load_repository_candidates_created_on_or_after,
|
|
33
32
|
parse_cli_date,
|
|
33
|
+
resolve_mapping_rules,
|
|
34
34
|
sourcegraph_datetime_filter,
|
|
35
35
|
user_ids_created_on_or_after,
|
|
36
36
|
write_maps_backup,
|
|
@@ -594,8 +594,13 @@ def cmd_set(
|
|
|
594
594
|
do_backup: bool,
|
|
595
595
|
retain_saml_group_users: bool = False,
|
|
596
596
|
worker_pool: ThreadPoolExecutor | None = None,
|
|
597
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
597
598
|
) -> run_context.CommandData:
|
|
598
|
-
"""Dispatch the selected set mode.
|
|
599
|
+
"""Dispatch the selected set mode.
|
|
600
|
+
|
|
601
|
+
`mapping_rules` carries in-memory rules from module callers; when None,
|
|
602
|
+
rules are loaded from `run_paths.maps_path`.
|
|
603
|
+
"""
|
|
599
604
|
options = set_options
|
|
600
605
|
if options.mode == "full":
|
|
601
606
|
return permissions_full_set.cmd_set_full(
|
|
@@ -613,6 +618,7 @@ def cmd_set(
|
|
|
613
618
|
do_backup=do_backup,
|
|
614
619
|
retain_saml_group_users=retain_saml_group_users,
|
|
615
620
|
worker_pool=worker_pool,
|
|
621
|
+
mapping_rules=mapping_rules,
|
|
616
622
|
)
|
|
617
623
|
if options.mode == "repos":
|
|
618
624
|
assert options.repository_names
|
|
@@ -631,6 +637,7 @@ def cmd_set(
|
|
|
631
637
|
do_backup=do_backup,
|
|
632
638
|
retain_saml_group_users=retain_saml_group_users,
|
|
633
639
|
worker_pool=worker_pool,
|
|
640
|
+
mapping_rules=mapping_rules,
|
|
634
641
|
)
|
|
635
642
|
if options.mode == "repos_without_explicit_perms":
|
|
636
643
|
return permissions_full_set.cmd_set_full(
|
|
@@ -648,6 +655,7 @@ def cmd_set(
|
|
|
648
655
|
do_backup=do_backup,
|
|
649
656
|
retain_saml_group_users=retain_saml_group_users,
|
|
650
657
|
worker_pool=worker_pool,
|
|
658
|
+
mapping_rules=mapping_rules,
|
|
651
659
|
)
|
|
652
660
|
if options.mode == "repos_created_after":
|
|
653
661
|
assert options.repository_created_after is not None
|
|
@@ -666,6 +674,7 @@ def cmd_set(
|
|
|
666
674
|
do_backup=do_backup,
|
|
667
675
|
retain_saml_group_users=retain_saml_group_users,
|
|
668
676
|
worker_pool=worker_pool,
|
|
677
|
+
mapping_rules=mapping_rules,
|
|
669
678
|
)
|
|
670
679
|
if options.mode == "users":
|
|
671
680
|
assert options.user_identifiers
|
|
@@ -680,6 +689,7 @@ def cmd_set(
|
|
|
680
689
|
saml_groups_attribute_name_by_config_id,
|
|
681
690
|
do_backup,
|
|
682
691
|
worker_pool,
|
|
692
|
+
mapping_rules=mapping_rules,
|
|
683
693
|
)
|
|
684
694
|
if options.mode == "users_without_explicit_perms":
|
|
685
695
|
return cmd_set_additive_users_without_explicit_perms(
|
|
@@ -693,6 +703,7 @@ def cmd_set(
|
|
|
693
703
|
saml_groups_attribute_name_by_config_id,
|
|
694
704
|
do_backup,
|
|
695
705
|
worker_pool,
|
|
706
|
+
mapping_rules=mapping_rules,
|
|
696
707
|
)
|
|
697
708
|
if options.mode == "created_after":
|
|
698
709
|
assert options.user_created_after is not None
|
|
@@ -706,6 +717,7 @@ def cmd_set(
|
|
|
706
717
|
saml_groups_attribute_name_by_config_id,
|
|
707
718
|
do_backup,
|
|
708
719
|
worker_pool,
|
|
720
|
+
mapping_rules=mapping_rules,
|
|
709
721
|
)
|
|
710
722
|
return run_context.CommandData()
|
|
711
723
|
|
|
@@ -721,6 +733,7 @@ def cmd_set_additive_users(
|
|
|
721
733
|
saml_groups_attribute_name_by_config_id: dict[str, str],
|
|
722
734
|
do_backup: bool,
|
|
723
735
|
worker_pool: ThreadPoolExecutor | None = None,
|
|
736
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
724
737
|
) -> run_context.CommandData:
|
|
725
738
|
"""Add missing mapped permissions for resolved users."""
|
|
726
739
|
with src.span(
|
|
@@ -732,7 +745,7 @@ def cmd_set_additive_users(
|
|
|
732
745
|
parallelism=parallelism,
|
|
733
746
|
do_backup=do_backup,
|
|
734
747
|
):
|
|
735
|
-
mapping_rules =
|
|
748
|
+
mapping_rules = resolve_mapping_rules(mapping_rules, run_paths.maps_path)
|
|
736
749
|
if not mapping_rules:
|
|
737
750
|
log.warning("No maps defined in %s — nothing to do.", run_paths.maps_path)
|
|
738
751
|
return run_context.CommandData()
|
|
@@ -844,6 +857,7 @@ def cmd_set_additive_users_without_explicit_perms(
|
|
|
844
857
|
saml_groups_attribute_name_by_config_id: dict[str, str],
|
|
845
858
|
do_backup: bool,
|
|
846
859
|
worker_pool: ThreadPoolExecutor | None = None,
|
|
860
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
847
861
|
) -> run_context.CommandData:
|
|
848
862
|
"""Add mapped permissions for users with no explicit API grants."""
|
|
849
863
|
created_after_filter: str | None = None
|
|
@@ -859,7 +873,7 @@ def cmd_set_additive_users_without_explicit_perms(
|
|
|
859
873
|
parallelism=parallelism,
|
|
860
874
|
do_backup=do_backup,
|
|
861
875
|
):
|
|
862
|
-
mapping_rules =
|
|
876
|
+
mapping_rules = resolve_mapping_rules(mapping_rules, run_paths.maps_path)
|
|
863
877
|
if not mapping_rules:
|
|
864
878
|
log.warning("No maps defined in %s — nothing to do.", run_paths.maps_path)
|
|
865
879
|
return run_context.CommandData()
|
|
@@ -1019,6 +1033,7 @@ def cmd_set_additive_created_after(
|
|
|
1019
1033
|
saml_groups_attribute_name_by_config_id: dict[str, str],
|
|
1020
1034
|
do_backup: bool,
|
|
1021
1035
|
worker_pool: ThreadPoolExecutor | None = None,
|
|
1036
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
1022
1037
|
) -> run_context.CommandData:
|
|
1023
1038
|
"""Add missing mapped permissions for users created on or after a date."""
|
|
1024
1039
|
created_after_filter = sourcegraph_datetime_filter(
|
|
@@ -1032,7 +1047,7 @@ def cmd_set_additive_created_after(
|
|
|
1032
1047
|
parallelism=parallelism,
|
|
1033
1048
|
do_backup=do_backup,
|
|
1034
1049
|
):
|
|
1035
|
-
mapping_rules =
|
|
1050
|
+
mapping_rules = resolve_mapping_rules(mapping_rules, run_paths.maps_path)
|
|
1036
1051
|
if not mapping_rules:
|
|
1037
1052
|
log.warning("No maps defined in %s — nothing to do.", run_paths.maps_path)
|
|
1038
1053
|
return run_context.CommandData()
|
|
@@ -21,12 +21,12 @@ from . import types as permission_types
|
|
|
21
21
|
from .workflow import (
|
|
22
22
|
load_mapping_context_discovery,
|
|
23
23
|
load_mapping_context_for_rules,
|
|
24
|
-
load_mapping_rules,
|
|
25
24
|
load_repository_candidates_by_names,
|
|
26
25
|
load_repository_candidates_created_on_or_after,
|
|
27
26
|
mapping_context_with_repository_candidates,
|
|
28
27
|
projected_snapshot_shell,
|
|
29
28
|
render_projected_snapshot_diff,
|
|
29
|
+
resolve_mapping_rules,
|
|
30
30
|
user_ids_created_on_or_after,
|
|
31
31
|
validate_post_apply,
|
|
32
32
|
write_maps_backup,
|
|
@@ -968,6 +968,7 @@ def cmd_set_full(
|
|
|
968
968
|
do_backup: bool,
|
|
969
969
|
retain_saml_group_users: bool,
|
|
970
970
|
worker_pool: ThreadPoolExecutor | None = None,
|
|
971
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
971
972
|
) -> run_context.CommandData:
|
|
972
973
|
"""Overwrite each mapped repo with the union of users from all rules."""
|
|
973
974
|
with src.span(
|
|
@@ -981,7 +982,7 @@ def cmd_set_full(
|
|
|
981
982
|
parallelism=parallelism,
|
|
982
983
|
do_backup=do_backup,
|
|
983
984
|
) as command_event:
|
|
984
|
-
mapping_rules =
|
|
985
|
+
mapping_rules = resolve_mapping_rules(mapping_rules, run_paths.maps_path)
|
|
985
986
|
if not mapping_rules:
|
|
986
987
|
_finish_empty_full_set_mapping_rules(
|
|
987
988
|
client,
|
{src_auth_perms_sync-0.5.0 → src_auth_perms_sync-0.6.0}/src/src_auth_perms_sync/permissions/maps.py
RENAMED
|
@@ -269,6 +269,28 @@ def create_maps_yaml_if_missing(path: Path) -> bool:
|
|
|
269
269
|
return True
|
|
270
270
|
|
|
271
271
|
|
|
272
|
+
def dump_mapping_rules_yaml(
|
|
273
|
+
path: Path,
|
|
274
|
+
mapping_rules: list[permission_types.MappingRule],
|
|
275
|
+
) -> None:
|
|
276
|
+
"""Write in-memory mapping rules as a maps YAML file for run auditability."""
|
|
277
|
+
content = yaml.safe_dump(
|
|
278
|
+
{"maps": mapping_rules},
|
|
279
|
+
sort_keys=False,
|
|
280
|
+
default_flow_style=False,
|
|
281
|
+
)
|
|
282
|
+
with src.span(
|
|
283
|
+
"disk_io",
|
|
284
|
+
level="DEBUG",
|
|
285
|
+
op="write",
|
|
286
|
+
path=str(path),
|
|
287
|
+
file_kind="yaml",
|
|
288
|
+
) as disk_event:
|
|
289
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
path.write_text(content)
|
|
291
|
+
disk_event["bytes"] = len(content)
|
|
292
|
+
|
|
293
|
+
|
|
272
294
|
def load_maps_yaml(path: Path) -> permission_types.ConfigFile:
|
|
273
295
|
with src.span(
|
|
274
296
|
"disk_io",
|
|
@@ -106,6 +106,22 @@ def load_mapping_rules(input_path: Path) -> list[permission_types.MappingRule]:
|
|
|
106
106
|
return mapping_rules
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def resolve_mapping_rules(
|
|
110
|
+
provided_rules: list[permission_types.MappingRule] | None,
|
|
111
|
+
maps_path: Path,
|
|
112
|
+
) -> list[permission_types.MappingRule]:
|
|
113
|
+
"""Return validated in-memory mapping rules, or load them from the maps file.
|
|
114
|
+
|
|
115
|
+
In-memory rules go through the same structural validation as rules read
|
|
116
|
+
from YAML, so module callers and CLI operators share one contract.
|
|
117
|
+
"""
|
|
118
|
+
if provided_rules is None:
|
|
119
|
+
return load_mapping_rules(maps_path)
|
|
120
|
+
if provided_rules:
|
|
121
|
+
permissions_mapping.validate_mapping_rules(provided_rules)
|
|
122
|
+
return provided_rules
|
|
123
|
+
|
|
124
|
+
|
|
109
125
|
def load_mapping_context(
|
|
110
126
|
client: src.SourcegraphClient,
|
|
111
127
|
input_path: Path,
|
|
@@ -28,6 +28,7 @@ import yaml
|
|
|
28
28
|
from src_py_lib.utils.config import config_options
|
|
29
29
|
|
|
30
30
|
from src_auth_perms_sync import cli
|
|
31
|
+
from src_auth_perms_sync.permissions import types as permission_types
|
|
31
32
|
from src_auth_perms_sync.shared import backups
|
|
32
33
|
from src_auth_perms_sync.shared import types as shared_types
|
|
33
34
|
|
|
@@ -782,8 +783,20 @@ def cli_input_for_case(
|
|
|
782
783
|
|
|
783
784
|
|
|
784
785
|
def run_fixture_case(
|
|
785
|
-
case_name: str,
|
|
786
|
+
case_name: str,
|
|
787
|
+
runner: str = "cli",
|
|
788
|
+
*,
|
|
789
|
+
no_files: bool = False,
|
|
790
|
+
mapping_rules: list[permission_types.MappingRule] | None = None,
|
|
791
|
+
preserve_artifacts_into: Path | None = None,
|
|
786
792
|
) -> FixtureRunResult:
|
|
793
|
+
"""Run one fixture case; `mapping_rules` exercises the in-memory Set path.
|
|
794
|
+
|
|
795
|
+
With `mapping_rules` set, the case's maps file is NOT passed to the run
|
|
796
|
+
(maps_path is cleared), proving the rules alone drive the command.
|
|
797
|
+
`preserve_artifacts_into` routes run artifacts to a caller-owned
|
|
798
|
+
directory (not cleaned up here) so tests can inspect their contents.
|
|
799
|
+
"""
|
|
787
800
|
case = load_e2e_cases()[case_name]
|
|
788
801
|
case_dir = FIXTURES_DIR / case_name
|
|
789
802
|
before_state = load_state(case_dir / "before.json")
|
|
@@ -799,9 +812,16 @@ def run_fixture_case(
|
|
|
799
812
|
# Route run artifacts (snapshots, maps copies, generated maps.yaml) into
|
|
800
813
|
# a per-case temporary directory so local test runs never pollute the
|
|
801
814
|
# repo's ./src-auth-perms-sync-runs tree; the directory is removed when
|
|
802
|
-
# the case finishes.
|
|
803
|
-
with
|
|
804
|
-
|
|
815
|
+
# the case finishes (unless the caller supplied its own directory).
|
|
816
|
+
with contextlib.ExitStack() as artifact_stack:
|
|
817
|
+
if preserve_artifacts_into is not None:
|
|
818
|
+
artifacts_dir = preserve_artifacts_into
|
|
819
|
+
else:
|
|
820
|
+
artifacts_dir = Path(
|
|
821
|
+
artifact_stack.enter_context(
|
|
822
|
+
tempfile.TemporaryDirectory(prefix="src-auth-perms-sync-case-")
|
|
823
|
+
)
|
|
824
|
+
)
|
|
805
825
|
try:
|
|
806
826
|
cli_input = cli_input_for_case(case, case_name, client.endpoint, runner)
|
|
807
827
|
# Local runs execute in-process against the in-memory fake, where
|
|
@@ -811,6 +831,8 @@ def run_fixture_case(
|
|
|
811
831
|
config_updates: dict[str, object] = {"parallelism": 1}
|
|
812
832
|
if no_files:
|
|
813
833
|
config_updates["no_files"] = True
|
|
834
|
+
if mapping_rules is not None:
|
|
835
|
+
config_updates["maps_path"] = None
|
|
814
836
|
local_config = cli_input.config.model_copy(update=config_updates)
|
|
815
837
|
command = cli.resolve_command(cli_input.command_name, local_config)
|
|
816
838
|
run_paths = backups.resolve_run_paths(
|
|
@@ -827,6 +849,7 @@ def run_fixture_case(
|
|
|
827
849
|
cast(src.SourcegraphClient, client),
|
|
828
850
|
run_paths,
|
|
829
851
|
worker_pool,
|
|
852
|
+
mapping_rules=mapping_rules,
|
|
830
853
|
)
|
|
831
854
|
except SystemExit as exception:
|
|
832
855
|
command_failure = f"SystemExit: {exception.code!r}"
|
|
@@ -8,9 +8,16 @@ structurally validated, including the live/performance ones.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import tempfile
|
|
11
12
|
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import cast
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
12
17
|
|
|
13
18
|
from src_auth_perms_sync import cli
|
|
19
|
+
from src_auth_perms_sync.permissions import types as permission_types
|
|
20
|
+
from src_auth_perms_sync.permissions.workflow import load_mapping_rules
|
|
14
21
|
from tests.e2e.case_runner import (
|
|
15
22
|
FIXTURES_DIR,
|
|
16
23
|
case_cli_arguments,
|
|
@@ -93,6 +100,83 @@ class LocalCaseTests(unittest.TestCase):
|
|
|
93
100
|
with self.subTest(case=case_name):
|
|
94
101
|
self.assertEqual("", run_local_replay_case(case_name))
|
|
95
102
|
|
|
103
|
+
def test_in_memory_mapping_rules_match_maps_file(self) -> None:
|
|
104
|
+
"""In-memory rules must drive set exactly like the same rules from YAML.
|
|
105
|
+
|
|
106
|
+
The same fixture case runs through the import API three ways: from
|
|
107
|
+
its maps.yaml file, from the equivalent parsed rules in memory (no
|
|
108
|
+
maps file passed at all), and from in-memory rules with no_files.
|
|
109
|
+
All three must plan identical mutations and end in identical
|
|
110
|
+
instance state. The files-enabled in-memory run must write the
|
|
111
|
+
rules it actually used into the run directory as the audit copy,
|
|
112
|
+
and the no_files run must write nothing.
|
|
113
|
+
"""
|
|
114
|
+
for case_name in ("full-overwrite-dry-run", "full-overwrite-unions"):
|
|
115
|
+
with self.subTest(case=case_name):
|
|
116
|
+
mapping_rules = load_mapping_rules(FIXTURES_DIR / case_name / "maps.yaml")
|
|
117
|
+
self.assertTrue(mapping_rules, "fixture case must define mapping rules")
|
|
118
|
+
|
|
119
|
+
from_file = run_fixture_case(case_name, "import")
|
|
120
|
+
from_memory = run_fixture_case(case_name, "import", mapping_rules=mapping_rules)
|
|
121
|
+
from_memory_no_files = run_fixture_case(
|
|
122
|
+
case_name,
|
|
123
|
+
"import",
|
|
124
|
+
mapping_rules=mapping_rules,
|
|
125
|
+
no_files=True,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.assertIsNone(from_file.failure)
|
|
129
|
+
self.assertIsNone(from_memory.failure)
|
|
130
|
+
self.assertIsNone(from_memory_no_files.failure)
|
|
131
|
+
self.assertEqual(from_file.actual_mutations, from_memory.actual_mutations)
|
|
132
|
+
self.assertEqual(from_file.actual_state, from_memory.actual_state)
|
|
133
|
+
self.assertEqual(from_file.actual_mutations, from_memory_no_files.actual_mutations)
|
|
134
|
+
self.assertEqual(from_file.actual_state, from_memory_no_files.actual_state)
|
|
135
|
+
|
|
136
|
+
audit_copies = [
|
|
137
|
+
name for name in from_memory.artifact_file_names if name.endswith("maps.yaml")
|
|
138
|
+
]
|
|
139
|
+
self.assertTrue(
|
|
140
|
+
audit_copies,
|
|
141
|
+
"in-memory run with files enabled must write the rules audit copy",
|
|
142
|
+
)
|
|
143
|
+
self.assertEqual(
|
|
144
|
+
(),
|
|
145
|
+
from_memory_no_files.artifact_file_names,
|
|
146
|
+
"in-memory run with no_files must write nothing",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def test_in_memory_mapping_rules_audit_copy_contains_rules_used(self) -> None:
|
|
150
|
+
"""The audit maps.yaml must contain exactly the in-memory rules used."""
|
|
151
|
+
case_name = "full-overwrite-dry-run"
|
|
152
|
+
mapping_rules = load_mapping_rules(FIXTURES_DIR / case_name / "maps.yaml")
|
|
153
|
+
|
|
154
|
+
with tempfile.TemporaryDirectory(prefix="rules-audit-") as temp_directory:
|
|
155
|
+
audit_directory = Path(temp_directory)
|
|
156
|
+
result = run_fixture_case(
|
|
157
|
+
case_name,
|
|
158
|
+
"import",
|
|
159
|
+
mapping_rules=mapping_rules,
|
|
160
|
+
preserve_artifacts_into=audit_directory,
|
|
161
|
+
)
|
|
162
|
+
self.assertIsNone(result.failure)
|
|
163
|
+
audit_copies = sorted(audit_directory.rglob("maps.yaml"))
|
|
164
|
+
self.assertEqual(1, len(audit_copies), "expected exactly one rules audit copy")
|
|
165
|
+
audit_content = yaml.safe_load(audit_copies[0].read_text())
|
|
166
|
+
self.assertEqual({"maps": mapping_rules}, audit_content)
|
|
167
|
+
|
|
168
|
+
def test_in_memory_mapping_rules_reject_invalid_structure(self) -> None:
|
|
169
|
+
"""Structurally invalid in-memory rules fail fast, before any mutation."""
|
|
170
|
+
case_name = "full-overwrite-unions"
|
|
171
|
+
invalid_rules = [{"name": "Broken", "users": {"unknownField": ["x"]}}]
|
|
172
|
+
result = run_fixture_case(
|
|
173
|
+
case_name,
|
|
174
|
+
"import",
|
|
175
|
+
mapping_rules=cast("list[permission_types.MappingRule]", invalid_rules),
|
|
176
|
+
)
|
|
177
|
+
self.assertIsNotNone(result.failure)
|
|
178
|
+
self.assertEqual(0, result.actual_mutations, "invalid rules must mutate nothing")
|
|
179
|
+
|
|
96
180
|
def test_no_files_set_dry_run_matches_files_enabled(self) -> None:
|
|
97
181
|
"""no_files must not change a set dry-run's decisions, and writes nothing.
|
|
98
182
|
|