diff --git a/README.md b/README.md index 9eeb55a..d39bd1c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -pyolite [![Build Status](https://travis-ci.org/PressLabs/pyolite.svg?branch=master)](https://travis-ci.org/PressLabs/pyolite) [![Coverage Status](https://coveralls.io/repos/PressLabs/pyolite/badge.png)](https://coveralls.io/r/PressLabs/pyolite) [![Downloads](https://pypip.in/d/pyolite/badge.png)](https://crate.io/packages/pyolite/) +pyolite [![Build Status](https://travis-ci.org/PressLabs/svg?branch=master)](https://travis-ci.org/PressLabs/pyolite) [![Coverage Status](https://coveralls.io/repos/PressLabs/pyolite/badge.png)](https://coveralls.io/r/PressLabs/pyolite) [![Downloads](https://pypip.in/d/pyolite/badge.png)](https://crate.io/packages/pyolite/) ======= # Python wrapper for gitolite @@ -33,6 +33,7 @@ This is required because Pyolite makes changes to files only inside the **repos* ### Repository API First, we need to initialize a `pyolite` object with the path to `gitolite`'s repository. + ```python from pyolite import Pyolite @@ -42,17 +43,27 @@ olite = Pyolite(admin_repository=admin_repository) ``` After that, we can create and get a repo using `create` and `get` methods. + ```python # create a repo repo = olite.repos.get('my_repo') repo = olite.repos.create('ydo') repo = olite.repos.get_or_create('second_repo') + +# List existing Pyolite repos +repos = olite.repos.all() +for repo_it in repos: + print repo_it.name ``` Every repo has an `users` object, in order to facilitate basic operations: adding, editing and removing users from a repository. + ```python print "Repo's users: %s" % repo.users +# list a repo's users +users_as_list = repo.users.list() + user = olite.users.create(name='bob', key_path="~/.ssh/third_rsa.pub") # add a new user @@ -94,6 +105,14 @@ vlad.keys.append('just put the key here') # check if user is admin or not print vlad.is_admin + +# list user's keys and repos +keys_as_list = vlad.list_keys() +repos_as_list = vlad.list_repos() + +# delete a user by name +deleted_user = olite.users.delete('username') +print deleted_user ``` If you need any help with this module, write me `vlad@presslabs.com` diff --git a/pyolite/managers/manager.py b/pyolite/managers/manager.py index 99099be..00238ea 100644 --- a/pyolite/managers/manager.py +++ b/pyolite/managers/manager.py @@ -2,7 +2,7 @@ from unipath import Path -from pyolite.git import Git +from git import Git class Manager(object): @@ -26,3 +26,7 @@ def get(self, entity): @abstractmethod def create(self, entity): raise NotImplementedError("Each manager needs a create method") + + @abstractmethod + def delete(self, entity_name): + raise NotImplementedError("Each manager needs a delete method") diff --git a/pyolite/managers/repository.py b/pyolite/managers/repository.py index db2424d..a072523 100644 --- a/pyolite/managers/repository.py +++ b/pyolite/managers/repository.py @@ -1,7 +1,7 @@ +import re from unipath import Path -from pyolite.models.repository import Repository - +from models.repository import Repository from .manager import Manager @@ -26,3 +26,28 @@ def create(self, lookup_repo): self.git.commit([str(repo_file)], 'Created repo %s' % lookup_repo) return Repository(lookup_repo, self.path, self.git) + + def delete(self, lookup_repo_name): + repo = Repository(lookup_repo_name, self.path, self.git) + dest = Path(self.path, 'conf/repos/%s.conf' % lookup_repo_name) + if not dest.exists(): + raise ValueError('Repository %s not existing.' % lookup_repo_name) + dest.remove() + self.git.commit([str(dest)], 'Deleted repo %s.' % lookup_repo_name) + + return repo + + def all(self): + repos = [] + repo_dir = Path(self.path, 'conf/repos') + + for obj in repo_dir.walk(): + if obj.isdir(): + continue + + files = re.compile('(\w+.conf$)').findall(str(obj)) + if files: + repos += files + + return [Repository.get_by_name(repo[:-5], self.path, self.git) + for repo in set(repos)] \ No newline at end of file diff --git a/pyolite/managers/user.py b/pyolite/managers/user.py index 0a8a165..f161cad 100644 --- a/pyolite/managers/user.py +++ b/pyolite/managers/user.py @@ -1,9 +1,8 @@ import re - from unipath import Path -from pyolite.models.user import User -from pyolite.managers.manager import Manager +from models.user import User +from managers.manager import Manager class UserManager(Manager): @@ -18,6 +17,16 @@ def create(self, name, key=None, key_path=None): def get(self, name): return User.get_by_name(name, self.path, self.git) + def delete(self, name): + user = User(self.path, self.git, name) + dest = Path(self.path, 'keydir/%s' % name) + if not dest.exists(): + raise ValueError('Repository %s not existing.' % name) + dest.rmtree() + self.git.commit([str(dest)], 'Deleted user %s.' % name) + + return user + def all(self): users = [] key_dir = Path(self.path, 'keydir') diff --git a/pyolite/models/lists/users.py b/pyolite/models/lists/users.py index b9ddf2b..83cff18 100644 --- a/pyolite/models/lists/users.py +++ b/pyolite/models/lists/users.py @@ -1,7 +1,8 @@ from unipath import Path +import re -from pyolite.repo import Repo -from pyolite.models.user import User +from repo import Repo +from models.user import User ACCEPTED_PERMISSIONS = set('RW+CD') @@ -48,7 +49,7 @@ def add(self, user, permission): @with_user def edit(self, user, permission): - pattern = r'(\s*)([RW+DC]*)(\s*)=(\s*)%s' % user.name + pattern = r'(\s*)([RW+DC]*)(\s*)=(\s*)%s\s+' % user.name string = r"\n %s = %s" % (permission, user.name) self.repo.replace(pattern, string) @@ -60,13 +61,26 @@ def edit(self, user, permission): @with_user def remove(self, user): - pattern = r'(\s*)([RW+DC]*)(\s*)=(\s*)%s' % user.name + pattern = r'(\s*)([RW+DC]*)(\s*)=(\s*)%s\s+' % user.name self.repo.replace(pattern, "") self.repository_model.git.commit(['conf'], "Deleted user %s from repository %s" % (user.name, self.repository_model.name)) + def list(self): + users = [] + for user in self.repo.users: + if user=="None": + continue + pattern = r'(\s*)([RW+DC]*)(\s*)=(\s*)%s\s+' % user + with open(str(self.repo.path)) as f: + config = f.read() + for match in re.compile(pattern).finditer(config): + perm = match.group(2) + users.append({"name":user,"permission":perm}) + return users + def __iter__(self): for user in self._user: yield user diff --git a/pyolite/models/repository.py b/pyolite/models/repository.py index 339693b..611aa63 100644 --- a/pyolite/models/repository.py +++ b/pyolite/models/repository.py @@ -1,6 +1,6 @@ from unipath import Path -from pyolite.models.lists import ListUsers +from models.lists import ListUsers class Repository(object): diff --git a/pyolite/models/user.py b/pyolite/models/user.py index 7f21c99..aec5bac 100644 --- a/pyolite/models/user.py +++ b/pyolite/models/user.py @@ -1,6 +1,6 @@ from unipath import Path -from pyolite.models.lists import ListKeys +from models.lists import ListKeys class User(object): @@ -54,6 +54,21 @@ def is_admin(self): return True return False + def list_keys(self): + keys = [] + for key in self.keys: + if key.isfile(): + with open(str(key)) as f: + cont = f.read() + keys.append({"name":key, "key":cont.replace("\n","")}) + return keys + + def list_repos(self): + repos = [] + for repo in self.repos: + repos.append(repo.name.replace(".conf", "")) + return repos + def __str__(self): return "< %s >" % self.name diff --git a/tests/lists/test_keys.py b/tests/lists/test_keys.py index 9c8df2e..7226e53 100644 --- a/tests/lists/test_keys.py +++ b/tests/lists/test_keys.py @@ -3,7 +3,7 @@ from mock import patch, MagicMock, call from nose.tools import eq_ -from pyolite.models.lists import ListKeys +from models.lists import ListKeys class TestKeyList(TestCase): @@ -21,7 +21,7 @@ def test_if_we_commit_after_a_key_append(self): mock_user.path = "path" mock_user.name = "test" - with patch.multiple('pyolite.models.lists.keys', Path=mock_path, + with patch.multiple('models.lists.keys', Path=mock_path, hashlib=mock_hashlib): keys = ListKeys(mock_user) @@ -66,7 +66,7 @@ def test_list_remove(self): mock_user.path = "path" mock_user.name = "test" - with patch.multiple('pyolite.models.lists.keys', Path=mock_path, + with patch.multiple('models.lists.keys', Path=mock_path, hashlib=mock_hashlib): keys = ListKeys(mock_user) diff --git a/tests/lists/test_users.py b/tests/lists/test_users.py index 83f651b..8724c9d 100644 --- a/tests/lists/test_users.py +++ b/tests/lists/test_users.py @@ -2,7 +2,7 @@ from mock import MagicMock, patch, call from nose.tools import raises -from pyolite.models.lists.users import ListUsers +from models.lists.users import ListUsers class TestUserList(Spec): @@ -18,7 +18,7 @@ def test_it_should_raise_ValueError_if_user_exists_when_we_add_him(self): mocked_user.get.return_value = MagicMock(name='another_user') mocked_repo.users = ['another_user'] - with patch.multiple('pyolite.models.lists.users', + with patch.multiple('models.lists.users', Repo=mocked_repo, User=mocked_user): repo_users = ListUsers(mocked_repository) repo_users.add('test', 'hiRW+') @@ -35,7 +35,7 @@ def test_if_we_add_invalid_permissions_it_should_raise_ValueError(self): mocked_user.get.return_value = MagicMock(name='another_user') mocked_repo.users = ['user'] - with patch.multiple('pyolite.models.lists.users', + with patch.multiple('models.lists.users', Repo=mocked_repo, User=mocked_user): repo_users = ListUsers(mocked_repository) repo_users.add('test', 'hiRW+') @@ -55,7 +55,7 @@ def test_it_should_add_a_new_user_to_repo_if_is_valid(self): mocked_user.get.return_value = mock_single_user mocked_repo.users = ['user'] - with patch.multiple('pyolite.models.lists.users', + with patch.multiple('models.lists.users', Repo=MagicMock(return_value=mocked_repo), User=mocked_user): repo_users = ListUsers(mocked_repository) @@ -83,7 +83,7 @@ def test_user_removing(self): mocked_user.get.return_value = mock_single_user mocked_repo.users = ['user'] - with patch.multiple('pyolite.models.lists.users', + with patch.multiple('models.lists.users', Repo=MagicMock(return_value=mocked_repo), User=mocked_user): repo_users = ListUsers(mocked_repository) @@ -110,7 +110,7 @@ def test_user_edit_permissions(self): mocked_user.get.return_value = mock_single_user mocked_repo.users = ['user'] - with patch.multiple('pyolite.models.lists.users', + with patch.multiple('models.lists.users', Repo=MagicMock(return_value=mocked_repo), User=mocked_user): repo_users = ListUsers(mocked_repository) diff --git a/tests/managers/test_manager.py b/tests/managers/test_manager.py index 5195a89..3aaab0a 100644 --- a/tests/managers/test_manager.py +++ b/tests/managers/test_manager.py @@ -3,7 +3,7 @@ from nose.tools import raises, eq_ from mock import MagicMock, patch -from pyolite.managers.manager import Manager +from managers.manager import Manager class TestManager(TestCase): @@ -15,10 +15,10 @@ def test_if_admin_repository_is_not_dir_it_should_raise_ValueError(self): mocked_path.isdir.return_value = False - with patch.multiple('pyolite.managers.manager', + with patch.multiple('managers.manager', Path=MagicMock(return_value=mocked_path), Git=MagicMock(return_value=mocked_git)): - with patch.multiple('pyolite.managers.manager.Manager', + with patch.multiple('managers.manager.Manager', __abstractmethods__=set()): Manager('/path/to/repo') @@ -33,10 +33,10 @@ def test_get_or_create_method(self): Manager.get = mocked_get Manager.create = mocked_create - with patch.multiple('pyolite.managers.manager', + with patch.multiple('managers.manager', Path=MagicMock(return_value=mocked_path), Git=MagicMock(return_value=mocked_git)): - with patch.multiple('pyolite.managers.manager.Manager', + with patch.multiple('managers.manager.Manager', __abstractmethods__=set()): manager = Manager('/path/to/admin/repo') @@ -49,10 +49,10 @@ def test_get_abstract_method_method(self): mocked_path = MagicMock() mocked_git = MagicMock() - with patch.multiple('pyolite.managers.manager', + with patch.multiple('managers.manager', Path=MagicMock(return_value=mocked_path), Git=MagicMock(return_value=mocked_git)): - with patch.multiple('pyolite.managers.manager.Manager', + with patch.multiple('managers.manager.Manager', __abstractmethods__=set()): manager = Manager('/path/to/admin/repo') @@ -63,10 +63,10 @@ def test_create_abstract_method_method(self): mocked_path = MagicMock() mocked_git = MagicMock() - with patch.multiple('pyolite.managers.manager', + with patch.multiple('managers.manager', Path=MagicMock(return_value=mocked_path), Git=MagicMock(return_value=mocked_git)): - with patch.multiple('pyolite.managers.manager.Manager', + with patch.multiple('managers.manager.Manager', __abstractmethods__=set()): manager = Manager('/path/to/admin/repo') diff --git a/tests/managers/test_repository.py b/tests/managers/test_repository.py index 3a9abdd..26ba9b6 100644 --- a/tests/managers/test_repository.py +++ b/tests/managers/test_repository.py @@ -4,7 +4,7 @@ from nose.tools import eq_, raises from tests.mocked_manager import MockManager, mocked_git, mocked_path -from pyolite.managers.repository import RepositoryManager +from managers.repository import RepositoryManager class TestRepositoryManager(TestCase): @@ -13,7 +13,7 @@ def test_get_repository(self): mocked_repository.get_by_name.return_value = 'my_repo' RepositoryManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.repository', + with patch.multiple('managers.repository', Repository=mocked_repository): repos = RepositoryManager('/path/to/admin/repo/') @@ -33,7 +33,7 @@ def test_create_new_repository_that_already_exists(self): mocked_file.exists.return_value = True RepositoryManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.repository', + with patch.multiple('managers.repository', Path=mocked_path, Repository=mocked_repository): repos = RepositoryManager('/path/to/admin/repo/') @@ -52,7 +52,7 @@ def test_it_should_commit_if_a_new_repository_was_succesfully_created(self): RepositoryManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.repository', + with patch.multiple('managers.repository', Path=mocked_path, Repository=mocked_repository): repos = RepositoryManager('/path/to/admin/repo/') diff --git a/tests/managers/test_user.py b/tests/managers/test_user.py index eb206ec..31de46b 100644 --- a/tests/managers/test_user.py +++ b/tests/managers/test_user.py @@ -3,14 +3,14 @@ from mock import MagicMock, patch, call from nose.tools import raises, eq_ -from pyolite.managers.user import UserManager +from managers.user import UserManager from tests.mocked_manager import MockManager, mocked_git, mocked_path class TestUserManager(TestCase): @raises(ValueError) def test_create_user_with_no_key(self): - with patch.multiple('pyolite.managers.manager', + with patch.multiple('managers.manager', Git=MagicMock(), Path=MagicMock()): users = UserManager('~/path/to/admin/gitolite/repo') @@ -20,7 +20,7 @@ def test_create_user_succesfully(self): mocked_user = MagicMock(return_value='test_username') UserManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.user', User=mocked_user, + with patch.multiple('managers.user', User=mocked_user, Manager=MagicMock()): users = UserManager('~/path/to/admin/gitolite/repo') @@ -33,7 +33,7 @@ def test_get_user(self): mocked_user.get_by_name.return_value = 'test_user' UserManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.user', User=mocked_user): + with patch.multiple('managers.user', User=mocked_user): users = UserManager('~/path/to/admin/gitolite/repo') eq_('test_user', users.get('test_user')) @@ -60,7 +60,7 @@ def test_get_all_users(self): mocked_key_dir.walk.return_value = [mocked_file, mocked_dir] UserManager.__bases__ = (MockManager, ) - with patch.multiple('pyolite.managers.user', User=mocked_user, + with patch.multiple('managers.user', User=mocked_user, Path=mocked_path, re=mocked_re): users = UserManager('~/path/to/admin/gitolite/repo') diff --git a/tests/models/test_repository.py b/tests/models/test_repository.py index acca461..c6b7811 100644 --- a/tests/models/test_repository.py +++ b/tests/models/test_repository.py @@ -1,7 +1,7 @@ from nose.tools import eq_ from mock import MagicMock, patch -from pyolite.models.repository import Repository +from models.repository import Repository class TestRepositoryModel(object): @@ -17,7 +17,7 @@ def test_it_should_be_possible_to_retrieve_by_name_a_repo(self): mocked_path.walk.return_value = [mocked_file, mocked_dir] - with patch.multiple('pyolite.models.repository', + with patch.multiple('models.repository', Path=MagicMock(return_value=mocked_path), ListUsers=MagicMock(return_value=mocked_users)): repo = Repository.get_by_name('new_one', 'simple_path', 'git') @@ -36,7 +36,7 @@ def test_if_we_find_only_directories_should_return_none(self): mocked_path.walk.return_value = [mocked_dir] - with patch.multiple('pyolite.models.repository', + with patch.multiple('models.repository', Path=MagicMock(return_value=mocked_path), ListUsers=MagicMock(return_value=mocked_users)): repo = Repository.get_by_name('new_one', 'simple_path', 'git') diff --git a/tests/models/test_user.py b/tests/models/test_user.py index a2fa285..65f6590 100644 --- a/tests/models/test_user.py +++ b/tests/models/test_user.py @@ -3,7 +3,7 @@ from mock import MagicMock, patch, call from nose.tools import eq_, raises -from pyolite.models.user import User +from models.user import User class TestUserModel(TestCase): @@ -30,7 +30,7 @@ def set_mocks(self): def test_if_a_user_can_be_retrieved_by_name(self): mocks = self.set_mocks() - with patch.multiple('pyolite.models.user', Path=mocks['path'], + with patch.multiple('models.user', Path=mocks['path'], ListKeys=mocks['keys']): user = User(mocks['initial_path'], mocks['git'], 'vtemian', [], @@ -55,7 +55,7 @@ def test_if_a_user_can_be_retrieved_by_name(self): def test_if_user_is_admin(self): mocks = self.set_mocks() - with patch.multiple('pyolite.models.user', Path=mocks['path'], + with patch.multiple('models.user', Path=mocks['path'], ListKeys=mocks['keys']): user = User(mocks['initial_path'], mocks['git'], 'vtemian', [], @@ -69,6 +69,6 @@ def test_if_user_is_admin(self): def test_get_user_by_nothing_it_should_raise_value_error(self): mocks = self.set_mocks() - with patch.multiple('pyolite.models.user', Path=mocks['path'], + with patch.multiple('models.user', Path=mocks['path'], ListKeys=mocks['keys']): User.get(MagicMock(), mocks['git'], mocks['path']) diff --git a/tests/test_git.py b/tests/test_git.py index be97198..b1687a1 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -3,7 +3,7 @@ from mock import MagicMock, patch, call from nose.tools import raises, eq_ -from pyolite.git import Git +from git import Git class TestGit(TestCase): @@ -17,7 +17,7 @@ def test_commit_with_no_message(self): mock_repo.index = mock_index mock_repo.remotes.origin = mock_remotes - with patch.multiple('pyolite.git', Repo=mock_repo): + with patch.multiple('git', Repo=mock_repo): git = Git('~/path/to/repo') objects = ['simple_object', 'more_complex_one'] @@ -31,7 +31,7 @@ def test_commit_succesfully_with_multiple_objects(self): mock_repo.index = mock_index mock_repo.remotes.origin = mock_remotes - with patch.multiple('pyolite.git', Repo=MagicMock(return_value=mock_repo)): + with patch.multiple('git', Repo=MagicMock(return_value=mock_repo)): git = Git('~/path/to/repo') objects = ['simple_object', 'more_complex_one'] diff --git a/tests/test_pyolite.py b/tests/test_pyolite.py index 65d6840..2144e32 100644 --- a/tests/test_pyolite.py +++ b/tests/test_pyolite.py @@ -11,10 +11,10 @@ def test_if_pyolite_object_has_all_attributes(self): mocked_repository = MagicMock() mocked_user = MagicMock() - with patch.multiple('pyolite.pyolite', RepositoryManager=mocked_repository, + with patch.multiple('pyolite', RepositoryManager=mocked_repository, UserManager=mocked_user): pyolite = Pyolite('my_repo') - eq_(pyolite.admin_repository, 'my_repo') + eq_(admin_repository, 'my_repo') mocked_repository.assert_called_once_with('my_repo') mocked_user.assert_called_once_with('my_repo') diff --git a/tests/test_repo.py b/tests/test_repo.py index e79a1ac..83727cf 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -3,7 +3,7 @@ from mock import MagicMock, patch from nose.tools import eq_ -from pyolite.repo import Repo +from repo import Repo class TestRepo(TestCase): @@ -13,7 +13,7 @@ def test_it_should_replace_a_given_string_in_repo_conf(self): mocked_re.sub.return_value = 'another_text' - with patch.multiple('pyolite.repo', re=mocked_re): + with patch.multiple('repo', re=mocked_re): repo = Repo(path) repo.replace('pattern', 'string') @@ -39,7 +39,7 @@ def test_it_should_retrieve_all_users_from_repo(self): mocked_user1.group.return_value = 'user1' mocked_user2.group.return_value = 'user2' - with patch.multiple('pyolite.repo', re=mocked_re): + with patch.multiple('repo', re=mocked_re): repo = Repo(mocked_path) eq_(repo.users, ['user1', 'user2'])