diff --git a/cheeto/__main__.py b/cheeto/__main__.py index 48c4dd1..10c181f 100644 --- a/cheeto/__main__.py +++ b/cheeto/__main__.py @@ -11,6 +11,7 @@ from . import __version__ from . import config +from . import database from . import hippo from . import monitor from . import log @@ -58,7 +59,15 @@ def main(): monitor_parser = commands.add_parser('monitor') monitor_commands = monitor_parser.add_subparsers() monitor.power(monitor_commands) - + + database_parser = commands.add_parser('database') + database_commands = database_parser.add_subparsers() + database.load(database_commands) + + query_parser = database_commands.add_parser('query') + query_commands = query_parser.add_subparsers() + database.query_users(query_commands) + args = parser.parse_args() with args.log.open('a') as log_fp: log.setup(log_fp, quiet=args.quiet) diff --git a/cheeto/config.py b/cheeto/config.py index 7cab622..6c05d6c 100644 --- a/cheeto/config.py +++ b/cheeto/config.py @@ -34,11 +34,22 @@ class LDAPConfig(BaseModel): group_attrs: Optional[Mapping[str, str]] = None +@require_kwargs +@dataclass(frozen=True) +class MongoConfig(BaseModel): + uri: str + port: int + user: str + password: str + database: str + collection: str + @require_kwargs @dataclass(frozen=True) class Config(BaseModel): ldap: Mapping[str, LDAPConfig] + mongo: MongoConfig def get_config_path() -> pathlib.Path: diff --git a/cheeto/database.py b/cheeto/database.py new file mode 100644 index 0000000..45dbc61 --- /dev/null +++ b/cheeto/database.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# (c) Camille Scott, 2023-2024 +# (c) The Regents of the University of California, Davis, 2023-2024 +# File : database.py +# License: Modified BSD +# Author : Camille Scott +# Date : 09.08.2024 + +import argparse +from dataclasses import field +import logging +from pathlib import Path +from typing import List, Mapping, Optional, Set, Tuple, TypedDict, Union + +from marshmallow import post_load +from marshmallow_dataclass import dataclass +from pymongo import MongoClient, ASCENDING +from pymongo.collection import Collection +from pymongo.database import Database as Database +from rich import print + +from .args import subcommand +from .config import MongoConfig +from .puppet import PuppetGroupRecord, PuppetUserRecord, SlurmRecord, add_repo_args, SiteData +from .types import (DEFAULT_SHELL, BaseModel, Date, KerberosID, LinuxGID, LinuxUID, + SEQUENCE_FIELDS, is_listlike, Shell, UserType) +from .utils import require_kwargs, __pkg_dir__ +from cheeto import puppet + + +@dataclass(frozen=True) +class GlobalUserRecord(BaseModel): + username: KerberosID + email: str + uid: LinuxUID + gid: LinuxGID + fullname: str + shell: Shell + home_directory: str + type: UserType + + _id: Optional[str] = None + + @post_load + def _set_id(self, in_data, **kwargs): + in_data['_id'] = in_data['username'] + return in_data + + @classmethod + def from_puppet(cls, username: str, puppet_record: PuppetUserRecord): + if puppet_record.groups is not None and 'hpccfgrp' in puppet_record.groups: #type: ignore + usertype = 'admin' + elif puppet_record.uid > 3000000000: + usertype = 'system' + else: + usertype = 'user' + + return cls.Schema().load(dict( + username = username, + email = puppet_record.email, + uid = puppet_record.uid, + gid = puppet_record.gid, + fullname = puppet_record.fullname, + shell = puppet_record.shell if puppet_record.shell else DEFAULT_SHELL, + home_directory = f'/home/{username}', + type = usertype + )) + + +@dataclass(frozen=True) +class SiteUserRecord(BaseModel): + username: KerberosID + expiry: Optional[Date] = None + slurm: Optional[SlurmRecord] = None + tag: Optional[Set[str]] = None + + _id: Optional[str] = None + + @post_load + def _set_id(self, in_data, **kwargs): + in_data['_id'] = in_data['username'] + return in_data + + @classmethod + def from_puppet(cls, username: str, puppet_record: PuppetUserRecord): + return cls.Schema().load(dict( + username = username, + expiry = puppet_record.expiry, + slurm = puppet_record.slurm, + tag = puppet_record.tag + )) + + +@dataclass(frozen=True) +class GlobalGroupRecord(BaseModel): + groupname: KerberosID + gid: LinuxGID + + _id: Optional[str] = None + + @post_load + def _set_id(self, in_data, **kwargs): + in_data['_id'] = in_data['groupname'] + return in_data + + @classmethod + def from_puppet(cls, groupname: str, puppet_record: PuppetGroupRecord): + return cls.Schema().load(dict( + groupname = groupname, + gid = puppet_record.gid + )) + + +@dataclass(frozen=True) +class SiteGroupRecord(BaseModel): + groupname: KerberosID + members: Optional[Set[KerberosID]] = field(default_factory=set) + sponsors: Optional[Set[KerberosID]] = field(default_factory=set) + slurm: Optional[SlurmRecord] = None + + _id: Optional[str] = None + + @post_load + def _set_id(self, in_data, **kwargs): + in_data['_id'] = in_data['groupname'] + return in_data + + @classmethod + def from_puppet(cls, groupname: str, puppet_record: PuppetGroupRecord): + return cls.Schema().load(dict( + groupname = groupname, + sponsors = puppet_record.sponsors, + slurm = puppet_record.slurm.to_dict() if puppet_record.slurm is not None else None + )) + + +@dataclass(frozen=True) +class SiteRecord(BaseModel): + sitename: str + fqdn: str + users: List[SiteUserRecord] + groups: List[SiteGroupRecord] + + _id: Optional[str] = None + + @post_load + def _set_id(self, in_data, **kwargs): + in_data['_id'] = in_data['sitename'] + return in_data + + +@dataclass(frozen=True) +class Collections(BaseModel): + users: List[GlobalUserRecord] + groups: List[GlobalGroupRecord] + sites: List[SiteRecord] + + +def get_database(config: MongoConfig): + client = MongoClient(config.uri, + username=config.user, + password=config.password) + return client[config.database] + + +def bootstrap_database(db: Database, bootstrap_yaml: Optional[Path] = None): + logger = logging.getLogger(__name__) + if bootstrap_yaml is None: + bootstrap_yaml = __pkg_dir__ / 'templates' / 'mongodb' / 'bootstrap.yaml' + data = Collections.load_yaml(bootstrap_yaml) + print(f'Loaded', data) + + sites = db['sites'] + for site in data.sites: + add_site(db, site) + sites.create_index('sitename', unique=True) + sites.create_index('users.username') + sites.create_index('groups.groupname') + + users = db['users'] + for user in data.users: + users.insert_one(user.to_dict()) + users.create_index('username', unique=True) + + groups = db['groups'] + for group in data.groups: + groups.insert_one(group.to_dict()) + groups.create_index('groupname', unique=True) + + +def add_site(db: Database, site: SiteRecord): + sites = db['sites'] + sites.insert_one(site.to_dict()) + + +def upsert_global_user(db: Database, user: GlobalUserRecord): + db.users.update_one({'username': user.username}, + {'$set': user.to_dict()}, + upsert=True) + + +def upsert_site_user(db: Database, sitename: str, user: SiteUserRecord): + result = db.sites.update_one({'sitename': sitename, 'users': {'$elemMatch': {'username': user.username}}}, + {'$set': { 'users.$': user.to_dict() }}) + if result.modified_count == 0: + db.sites.update_one({'sitename': sitename}, + {'$addToSet': {'users': user.to_dict()}}, + upsert=True) + + +def upsert_global_group(db: Database, group: GlobalGroupRecord): + db.groups.update_one({'groupname': group.groupname}, + {'$set': group.to_dict()}, + upsert=True) + + +def upsert_site_group(db: Database, sitename: str, group: SiteGroupRecord): + result = db.sites.update_one({'sitename': sitename, 'groups': {'$elemMatch': {'groupname': group.groupname}}}, + {'$set': { 'groups.$': group.to_dict() }}) + if result.modified_count == 0: + db.sites.update_one({'sitename': sitename}, + {'$addToSet': {'groups': group.to_dict()}}, + upsert=True) + + +def add_load_args(parser: argparse.ArgumentParser): + parser.add_argument('--sitename', required=True) + + +@subcommand('load', add_repo_args, add_load_args) +def load(args: argparse.Namespace): + logger = logging.getLogger(__name__) + + site_data = SiteData(args.site_dir, + common_root=args.global_dir, + key_dir=args.key_dir, + load=False) + db = get_database(args.config.mongo) + + with site_data.lock(args.timeout): + site_data.load() + + for group_name, group_record in site_data.iter_groups(): + global_record = GlobalGroupRecord.from_puppet(group_name, group_record) + upsert_global_group(db, global_record) #type: ignore + + site_record = SiteGroupRecord.from_puppet(group_name, group_record) + print(site_record) + upsert_site_group(db, args.sitename, site_record) #type: ignore + + for user_name, user_record in site_data.iter_users(): + global_record = GlobalUserRecord.from_puppet(user_name, user_record) + upsert_global_user(db, global_record) #type: ignore + + site_record = SiteUserRecord.from_puppet(user_name, user_record) + print(site_record) + upsert_site_user(db, args.sitename, site_record) #type: ignore + + +def add_query_args(parser: argparse.ArgumentParser): + parser.add_argument('--in', dest='val_in', nargs='+', action='append') + parser.add_argument('--not-in', dest='val_not_in', nargs='+', action='append') + parser.add_argument('--all', default=False, action='store_true') + + +def build_query(args: argparse.Namespace): + query = {} + if args.val_in: + for query_group in args.val_in: + #if query_group[0] not in record_type.field_names(): + # raise ValueError(f'Query key must be one of: {record_type.field_names()}') + query[query_group[0]] = {'$in': query_group[1:]} + if args.val_not_in: + for query_group in args.val_not_in: + #if query_group[0] not in record_type.field_names(): + # raise ValueError(f'Query key must be one of: {record_type.field_names()}') + query[query_group[0]] = {'$nin': query_group[1:]} + return query + + + +def add_query_user_args(parser: argparse.ArgumentParser): + + parser.add_argument('--site') + + +@subcommand('users', add_query_args) +def query_users(args: argparse.Namespace): + db = get_database(args.config.mongo) + + if args.all: + search = db.users.find() + for record in search: + record = GlobalUserRecord.Schema().load(record) + print(record) + else: + query = build_query(args) + search = db.users.find(query) + for record in search: + record = GlobalUserRecord.Schema().load(record) + print(record) + diff --git a/cheeto/puppet.py b/cheeto/puppet.py index 4e461c1..0006aa2 100644 --- a/cheeto/puppet.py +++ b/cheeto/puppet.py @@ -581,6 +581,10 @@ def iter_users(self): for user_name, user_record in self.data.user.items(): #type: ignore yield user_name, user_record + def iter_groups(self): + for group_name, group_record in self.data.group.items(): #type: ignore + yield group_name, group_record + def add_validate_args(parser: argparse.ArgumentParser): group = parser.add_argument_group('YAML Validation') diff --git a/cheeto/templates/mongodb/bootstrap.yaml b/cheeto/templates/mongodb/bootstrap.yaml new file mode 100644 index 0000000..d27fc86 --- /dev/null +++ b/cheeto/templates/mongodb/bootstrap.yaml @@ -0,0 +1,69 @@ +users: + - username: camw + email: cswel@ucdavis.edu + uid: 1134153 + gid: 1134153 + fullname: Camille Scott + shell: /bin/zsh + home_directory: /home/camw + type: admin +groups: + - groupname: hpccfgrp + gid: 4100000000 +sites: + - sitename: hive + fqdn: hive.hpc.ucdavis.edu + users: + - username: camw + tag: + - root-ssh-tag + - ssh-tag + - sudo-tag + groups: + - groupname: hpccfgrp + members: + - camw + sponsors: + - camw + - sitename: farm + fqdn: farm.hpc.ucdavis.edu + users: + - username: camw + tag: + - root-ssh-tag + - ssh-tag + - sudo-tag + groups: + - groupname: hpccfgrp + members: + - camw + sponsors: + - camw + - sitename: franklin + fqdn: franklin.hpc.ucdavis.edu + users: + - username: camw + tag: + - root-ssh-tag + - ssh-tag + - sudo-tag + groups: + - groupname: hpccfgrp + members: + - camw + sponsors: + - camw + - sitename: peloton + fqdn: peloton.hpc.ucdavis.edu + users: + - username: camw + tag: + - root-ssh-tag + - ssh-tag + - sudo-tag + groups: + - groupname: hpccfgrp + members: + - camw + sponsors: + - camw diff --git a/cheeto/types.py b/cheeto/types.py index c882eb2..5cfd5c9 100644 --- a/cheeto/types.py +++ b/cheeto/types.py @@ -40,6 +40,10 @@ "/bin/false", "/usr/sbin/nologin"} +USER_TYPES = {'user', + 'admin', + 'system'} + def is_listlike(obj): return isinstance(obj, Sequence) and not isinstance(obj, (str, bytes, bytearray)) @@ -106,10 +110,17 @@ def save_yaml(self, filename: Path): def to_raw_yaml(self): return type(self).Schema().dump(self) #type: ignore + def to_dict(self): + return self.to_raw_yaml() + @classmethod def from_other(cls, other, **kwargs): return cls.Schema().load(puppet_merge(other.to_raw_yaml(), dict(**kwargs))) #type: ignore + @classmethod + def field_names(cls): + return set(cls.__dataclass_fields__.keys()) - {'Schema'} + @dataclass(frozen=True) class BaseModel(_BaseModel): @@ -167,6 +178,8 @@ def __init__(self, *args, **kwargs): SlurmQOSFlag = Annotated[str, mf.String(validate=mv.OneOf(SlurmQOSValidFlags))] +UserType = Annotated[str, mf.String(validate=mv.OneOf(USER_TYPES))] + SEQUENCE_FIELDS = { marshmallow_dataclass.collection_field.Sequence, diff --git a/poetry.lock b/poetry.lock index 7fd233c..97330d5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -40,6 +40,26 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "executing" version = "2.0.1" @@ -563,6 +583,78 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pymongo" +version = "4.8.0" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.8" +files = [ + {file = "pymongo-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2b7bec27e047e84947fbd41c782f07c54c30c76d14f3b8bf0c89f7413fac67a"}, + {file = "pymongo-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c68fe128a171493018ca5c8020fc08675be130d012b7ab3efe9e22698c612a1"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920d4f8f157a71b3cb3f39bc09ce070693d6e9648fb0e30d00e2657d1dca4e49"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b4108ac9469febba18cea50db972605cc43978bedaa9fea413378877560ef8"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:180d5eb1dc28b62853e2f88017775c4500b07548ed28c0bd9c005c3d7bc52526"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec2b9088cdbceb87e6ca9c639d0ff9b9d083594dda5ca5d3c4f6774f4c81b33"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0cf61450feadca81deb1a1489cb1a3ae1e4266efd51adafecec0e503a8dcd84"}, + {file = "pymongo-4.8.0-cp310-cp310-win32.whl", hash = "sha256:8b18c8324809539c79bd6544d00e0607e98ff833ca21953df001510ca25915d1"}, + {file = "pymongo-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e5df28f74002e37bcbdfdc5109799f670e4dfef0fb527c391ff84f078050e7b5"}, + {file = "pymongo-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b50040d9767197b77ed420ada29b3bf18a638f9552d80f2da817b7c4a4c9c68"}, + {file = "pymongo-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417369ce39af2b7c2a9c7152c1ed2393edfd1cbaf2a356ba31eb8bcbd5c98dd7"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf821bd3befb993a6db17229a2c60c1550e957de02a6ff4dd0af9476637b2e4d"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9365166aa801c63dff1a3cb96e650be270da06e3464ab106727223123405510f"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8b8582f4209c2459b04b049ac03c72c618e011d3caa5391ff86d1bda0cc486"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e5019f75f6827bb5354b6fef8dfc9d6c7446894a27346e03134d290eb9e758"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5802151fc2b51cd45492c80ed22b441d20090fb76d1fd53cd7760b340ff554"}, + {file = "pymongo-4.8.0-cp311-cp311-win32.whl", hash = "sha256:4bf58e6825b93da63e499d1a58de7de563c31e575908d4e24876234ccb910eba"}, + {file = "pymongo-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b747c0e257b9d3e6495a018309b9e0c93b7f0d65271d1d62e572747f4ffafc88"}, + {file = "pymongo-4.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e6a720a3d22b54183352dc65f08cd1547204d263e0651b213a0a2e577e838526"}, + {file = "pymongo-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31e4d21201bdf15064cf47ce7b74722d3e1aea2597c6785882244a3bb58c7eab"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b804bb4f2d9dc389cc9e827d579fa327272cdb0629a99bfe5b83cb3e269ebf"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fbdb87fe5075c8beb17a5c16348a1ea3c8b282a5cb72d173330be2fecf22f5"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd39455b7ee70aabee46f7399b32ab38b86b236c069ae559e22be6b46b2bbfc4"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940d456774b17814bac5ea7fc28188c7a1338d4a233efbb6ba01de957bded2e8"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:236bbd7d0aef62e64caf4b24ca200f8c8670d1a6f5ea828c39eccdae423bc2b2"}, + {file = "pymongo-4.8.0-cp312-cp312-win32.whl", hash = "sha256:47ec8c3f0a7b2212dbc9be08d3bf17bc89abd211901093e3ef3f2adea7de7a69"}, + {file = "pymongo-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84bc7707492f06fbc37a9f215374d2977d21b72e10a67f1b31893ec5a140ad8"}, + {file = "pymongo-4.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:519d1bab2b5e5218c64340b57d555d89c3f6c9d717cecbf826fb9d42415e7750"}, + {file = "pymongo-4.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87075a1feb1e602e539bdb1ef8f4324a3427eb0d64208c3182e677d2c0718b6f"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f53429515d2b3e86dcc83dadecf7ff881e538c168d575f3688698a8707b80a"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdc20cd1e1141b04696ffcdb7c71e8a4a665db31fe72e51ec706b3bdd2d09f36"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:284d0717d1a7707744018b0b6ee7801b1b1ff044c42f7be7a01bb013de639470"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bf0eb8b6ef40fa22479f09375468c33bebb7fe49d14d9c96c8fd50355188b0"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ecd71b9226bd1d49416dc9f999772038e56f415a713be51bf18d8676a0841c8"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0061af6e8c5e68b13f1ec9ad5251247726653c5af3c0bbdfbca6cf931e99216"}, + {file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:658d0170f27984e0d89c09fe5c42296613b711a3ffd847eb373b0dbb5b648d5f"}, + {file = "pymongo-4.8.0-cp38-cp38-win32.whl", hash = "sha256:3ed1c316718a2836f7efc3d75b4b0ffdd47894090bc697de8385acd13c513a70"}, + {file = "pymongo-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:7148419eedfea9ecb940961cfe465efaba90595568a1fb97585fb535ea63fe2b"}, + {file = "pymongo-4.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8400587d594761e5136a3423111f499574be5fd53cf0aefa0d0f05b180710b0"}, + {file = "pymongo-4.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af3e98dd9702b73e4e6fd780f6925352237f5dce8d99405ff1543f3771201704"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de3a860f037bb51f968de320baef85090ff0bbb42ec4f28ec6a5ddf88be61871"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fc18b3a093f3db008c5fea0e980dbd3b743449eee29b5718bc2dc15ab5088bb"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18c9d8f975dd7194c37193583fd7d1eb9aea0c21ee58955ecf35362239ff31ac"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:408b2f8fdbeca3c19e4156f28fff1ab11c3efb0407b60687162d49f68075e63c"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6564780cafd6abeea49759fe661792bd5a67e4f51bca62b88faab497ab5fe89"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d18d86bc9e103f4d3d4f18b85a0471c0e13ce5b79194e4a0389a224bb70edd53"}, + {file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9097c331577cecf8034422956daaba7ec74c26f7b255d718c584faddd7fa2e3c"}, + {file = "pymongo-4.8.0-cp39-cp39-win32.whl", hash = "sha256:d5428dbcd43d02f6306e1c3c95f692f68b284e6ee5390292242f509004c9e3a8"}, + {file = "pymongo-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:ef7225755ed27bfdb18730c68f6cb023d06c28f2b734597480fb4c0e500feb6f"}, + {file = "pymongo-4.8.0.tar.gz", hash = "sha256:454f2295875744dc70f1881e4b2eb99cdad008a33574bc8aaf120530f66c0cde"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] +docs = ["furo (==2023.9.10)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-rtd-theme (>=2,<3)", "sphinxcontrib-shellcheck (>=1,<2)"] +encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + [[package]] name = "pytest" version = "7.4.4" @@ -803,4 +895,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "4a7aa0e624690274b1bfd4d1d5cc967f71df1729786a81f9c22ee08c9385b08a" +content-hash = "7a02d50a54404698e3ae9e217e4ba42c2948e7be00f6516430274b9cc3f1a358" diff --git a/pyproject.toml b/pyproject.toml index 25e7d3d..b976850 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ filelock = "^3.12.0" python-ldap = "^3.4.3" ldap3 = {git = "https://github.com/ucdavis/ldap3.git", rev = "dev"} gssapi = "^1.8.3" +pymongo = "^4.8.0" [build-system] requires = ["poetry-core"]