Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ snapshots that make `--apply` reversible.

```bash
src-auth-perms-sync sync-saml-orgs --users alice,bob
src-auth-perms-sync sync-saml-orgs --created-after 2026-06-01
src-auth-perms-sync sync-saml-orgs --users-created-after 2026-06-01
src-auth-perms-sync sync-saml-orgs --users-without-explicit-perms
```

Expand All @@ -311,7 +311,7 @@ snapshots that make `--apply` reversible.
org whose SAML group disappeared has all members removed, but the org itself
is kept (its settings survive in case the group comes back).
- **Scoped** (user filters on `sync-saml-orgs`, or `set --users` /
`--users-without-explicit-perms` / `--created-after` with
`--users-without-explicit-perms` / `--users-created-after` with
`--sync-saml-orgs`): syncs org membership for exactly the selected users -
per-user additions AND removals, computed from each user's own SAML
assertion and org list. Other users' memberships never change, and no full
Expand Down
185 changes: 96 additions & 89 deletions src/src_auth_perms_sync/cli.py

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions src/src_auth_perms_sync/permissions/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def cmd_get(
if users_without_explicit_perms:
cmd_fields["users_without_explicit_perms"] = True
if user_created_after is not None:
cmd_fields["created_after"] = user_created_after
cmd_fields["users_created_after"] = user_created_after
if repository_names:
cmd_fields["repositories"] = repository_names
if repositories_without_explicit_perms:
Expand Down Expand Up @@ -438,7 +438,7 @@ def load_selected_users(
created_after_filter: str | None = None
if user_created_after is not None:
created_after_filter = sourcegraph_datetime_filter(
parse_cli_date(user_created_after, "--created-after")
parse_cli_date(user_created_after, "--users-created-after")
)
if users_without_explicit_perms:
candidate_selection = (
Expand Down Expand Up @@ -749,9 +749,9 @@ def cmd_set(
worker_pool=worker_pool,
mapping_rules=mapping_rules,
)
if options.mode == "created_after":
if options.mode == "users_created_after":
assert options.user_created_after is not None
return cmd_set_additive_created_after(
return cmd_set_additive_users_created_after(
client,
run_paths,
options.user_created_after,
Expand Down Expand Up @@ -912,7 +912,7 @@ def cmd_set_additive_users_without_explicit_perms(
created_after_filter: str | None = None
if user_created_after is not None:
created_after_filter = sourcegraph_datetime_filter(
parse_cli_date(user_created_after, "--created-after")
parse_cli_date(user_created_after, "--users-created-after")
)
with src.span(
"cmd_set_additive_users_without_explicit_perms",
Expand Down Expand Up @@ -1074,7 +1074,7 @@ def cmd_set_additive_users_without_explicit_perms(
return _additive_command_data(context, users, retain_saml_group_users)


def cmd_set_additive_created_after(
def cmd_set_additive_users_created_after(
client: src.SourcegraphClient,
run_paths: backups.RunPaths,
user_created_after: str,
Expand All @@ -1089,10 +1089,10 @@ def cmd_set_additive_created_after(
) -> run_context.CommandData:
"""Add missing mapped permissions for users created on or after a date."""
created_after_filter = sourcegraph_datetime_filter(
parse_cli_date(user_created_after, "--created-after")
parse_cli_date(user_created_after, "--users-created-after")
)
with src.span(
"cmd_set_additive_created_after",
"cmd_set_additive_users_created_after",
input_path=str(run_paths.maps_path),
user_created_after=user_created_after,
dry_run=dry_run,
Expand Down
2 changes: 1 addition & 1 deletion src/src_auth_perms_sync/permissions/full_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def _filter_full_set_users_by_created_at(
users: list[shared_types.User],
user_created_after: str | None,
) -> list[shared_types.User]:
"""Apply the optional created-after user filter."""
"""Apply the optional users-created-after user filter."""
if user_created_after is None:
return users

Expand Down
16 changes: 10 additions & 6 deletions src/src_auth_perms_sync/permissions/sourcegraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,16 @@ def fetch_batch(batch: Sequence[str]) -> list[shared_types.User | None]:

def list_site_user_candidates(
client: src.SourcegraphClient,
created_after: str | None,
users_created_after: str | None,
*,
parallelism: int = 1,
worker_pool: ThreadPoolExecutor | None = None,
) -> list[shared_types.SiteUserCandidate]:
"""Return non-deleted site users, optionally filtered by creation time."""
created_filter = {"gte": created_after} if created_after is not None else None
created_filter_label = f" created on or after {created_after}" if created_after else ""
created_filter = {"gte": users_created_after} if users_created_after is not None else None
created_filter_label = (
f" created on or after {users_created_after}" if users_created_after else ""
)
log.info("Querying active Sourcegraph user candidates%s ...", created_filter_label)
started = time.perf_counter()
first_page, total_count = _site_user_candidate_page(
Expand Down Expand Up @@ -322,7 +324,7 @@ def fetch_page(offset: int) -> tuple[int, list[shared_types.SiteUserCandidate]]:

def list_site_user_candidates_without_explicit_repos(
client: src.SourcegraphClient,
created_after: str | None,
users_created_after: str | None,
*,
batch_size: int,
parallelism: int,
Expand All @@ -337,8 +339,10 @@ def list_site_user_candidates_without_explicit_repos(
if batch_size < 1:
raise ValueError("batch_size must be at least 1")

created_filter = {"gte": created_after} if created_after is not None else None
created_filter_label = f" created on or after {created_after}" if created_after else ""
created_filter = {"gte": users_created_after} if users_created_after is not None else None
created_filter_label = (
f" created on or after {users_created_after}" if users_created_after else ""
)
log.info("Querying active Sourcegraph user candidates%s ...", created_filter_label)
started = time.perf_counter()
first_page, total_count = _site_user_candidate_page(
Expand Down
2 changes: 1 addition & 1 deletion src/src_auth_perms_sync/permissions/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"full",
"users",
"users_without_explicit_perms",
"created_after",
"users_created_after",
"repos",
"repos_without_explicit_perms",
"repos_created_after",
Expand Down
2 changes: 1 addition & 1 deletion src/src_auth_perms_sync/permissions/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ def sourcegraph_datetime_filter(value: datetime.datetime) -> str:

def user_ids_created_on_or_after(client: src.SourcegraphClient, value: str) -> set[str]:
"""Return Sourcegraph user IDs created on or after the given CLI date."""
filter_value = sourcegraph_datetime_filter(parse_cli_date(value, "--created-after"))
filter_value = sourcegraph_datetime_filter(parse_cli_date(value, "--users-created-after"))
candidates = permissions_sourcegraph.list_site_user_candidates(client, filter_value)
log.info(
"Restricting to %d Sourcegraph user(s) created on or after %s.",
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Live cases declare their identity preconditions in tests.yaml:
a pointer to setup.py on drift) and `live.temporaryUsers` (the harness
creates the named users fresh via `createUser` - `created_at` = now - and
hard-deletes them afterwards; `{today}` in a cliCommand resolves to the
run's UTC date, which makes positive `--created-after` selection
run's UTC date, which makes positive `--users-created-after` selection
deterministic against the long-pre-existing synthetic users).

## PyPI install smoke (`--install`)
Expand Down
6 changes: 3 additions & 3 deletions tests/e2e/case_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,15 +536,15 @@ def _repository_candidates(self, variables: dict[str, object]) -> list[dict[str,

def _site_users(self, variables: dict[str, object]) -> dict[str, Any]:
created_at_filter = variables.get("createdAt")
created_after: str | None = None
users_created_after: str | None = None
if isinstance(created_at_filter, dict):
created_after_value = cast(dict[str, object], created_at_filter).get("gte")
if isinstance(created_after_value, str):
created_after = created_after_value
users_created_after = created_after_value
candidates = [
user
for user in self._users
if created_after is None or user["createdAt"] >= created_after
if users_created_after is None or user["createdAt"] >= users_created_after
]
offset = self._integer_variable(variables, "offset")
# Serve pages no wider than SITE_USERS_PAGE_CAP regardless of the
Expand Down
9 changes: 8 additions & 1 deletion tests/integration/test_cli_entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ def test_command_help_prints_command_specific_options(self) -> None:
self.assertIn("--users USERS", set_help.stdout)
self.assertIn("--sync-saml-orgs", set_help.stdout)
self.assertNotIn("--restore-path", set_help.stdout)
self.assertIn("Permission sync:", set_help.stdout)
self.assertIn("Set scope (required: pass --full or filters):", set_help.stdout)
self.assertLess(
set_help.stdout.index("\nSet scope"),
set_help.stdout.index("\nOrganization sync:"),
)
self.assertNotIn("\nUser filters:", set_help.stdout)
self.assertNotIn("\nRepo filters:", set_help.stdout)
self.assertIn("Files:", set_help.stdout)
self.assertIn("Organization sync:", set_help.stdout)
self.assertIn("Sourcegraph:", set_help.stdout)
self.assertIn("Logging:", set_help.stdout)
Expand Down
2 changes: 1 addition & 1 deletion tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1883,7 +1883,7 @@ def run_seeded_fixture_apply(
self.delete_temporary_user(label, level, username, user_id)

def create_temporary_user(self, username: str) -> str | None:
"""Create a throwaway user (created_at = now) for created-after cases."""
"""Create a throwaway user (created_at = now) for users-created-after cases."""
try:
data = self.graphql(
"mutation TestCreateUser($username: String!) {"
Expand Down
Loading