Closes #9 Co-authored-by: Rio6 <rio.liu@r26.me> Co-authored-by: Rio Liu <rio.liu@r26.me> Co-authored-by: Max Erenberg <merenber@csclub.uwaterloo.ca> Reviewed-on: #11 Co-authored-by: Rio <r345liu@localhost> Co-committed-by: Rio <r345liu@localhost>
This commit is contained in:
parent
82b7b2c015
commit
651988bb08
|
@ -13,6 +13,9 @@ from ceo_common.model import Config, HTTPClient
|
||||||
|
|
||||||
|
|
||||||
def register_services():
|
def register_services():
|
||||||
|
# Using base component directly so events get triggered
|
||||||
|
baseComponent = component.getGlobalSiteManager()
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
# This is a hack to determine if we're in the dev env or not
|
# This is a hack to determine if we're in the dev env or not
|
||||||
if socket.getfqdn().endswith('.csclub.internal'):
|
if socket.getfqdn().endswith('.csclub.internal'):
|
||||||
|
@ -21,11 +24,11 @@ def register_services():
|
||||||
else:
|
else:
|
||||||
config_file = os.environ.get('CEO_CONFIG', '/etc/csc/ceo.ini')
|
config_file = os.environ.get('CEO_CONFIG', '/etc/csc/ceo.ini')
|
||||||
cfg = Config(config_file)
|
cfg = Config(config_file)
|
||||||
component.provideUtility(cfg, IConfig)
|
baseComponent.registerUtility(cfg, IConfig)
|
||||||
|
|
||||||
# HTTPService
|
# HTTPService
|
||||||
http_client = HTTPClient()
|
http_client = HTTPClient()
|
||||||
component.provideUtility(http_client, IHTTPClient)
|
baseComponent.registerUtility(http_client, IHTTPClient)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -2,6 +2,7 @@ import click
|
||||||
|
|
||||||
from .members import members
|
from .members import members
|
||||||
from .groups import groups
|
from .groups import groups
|
||||||
|
from .positions import positions
|
||||||
from .updateprograms import updateprograms
|
from .updateprograms import updateprograms
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,4 +13,5 @@ def cli():
|
||||||
|
|
||||||
cli.add_command(members)
|
cli.add_command(members)
|
||||||
cli.add_command(groups)
|
cli.add_command(groups)
|
||||||
|
cli.add_command(positions)
|
||||||
cli.add_command(updateprograms)
|
cli.add_command(updateprograms)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import click
|
||||||
|
from zope import component
|
||||||
|
|
||||||
|
from ..utils import http_get, http_post
|
||||||
|
from .utils import handle_sync_response, handle_stream_response, print_colon_kv
|
||||||
|
from ceo_common.interfaces import IConfig
|
||||||
|
from ceod.transactions.members import UpdateMemberPositionsTransaction
|
||||||
|
|
||||||
|
|
||||||
|
@click.group(short_help='List or change exec positions')
|
||||||
|
def positions():
|
||||||
|
update_commands()
|
||||||
|
|
||||||
|
|
||||||
|
@positions.command(short_help='Get current positions')
|
||||||
|
def get():
|
||||||
|
resp = http_get('/api/positions')
|
||||||
|
result = handle_sync_response(resp)
|
||||||
|
print_colon_kv(result.items())
|
||||||
|
|
||||||
|
|
||||||
|
@positions.command(short_help='Update positions')
|
||||||
|
def set(**kwargs):
|
||||||
|
body = {k.replace('_', '-'): v for k, v in kwargs.items()}
|
||||||
|
print_body = {k: v or '' for k, v in body.items()}
|
||||||
|
click.echo('The positions will be updated:')
|
||||||
|
print_colon_kv(print_body.items())
|
||||||
|
click.confirm('Do you want to continue?', abort=True)
|
||||||
|
|
||||||
|
resp = http_post('/api/positions', json=body)
|
||||||
|
handle_stream_response(resp, UpdateMemberPositionsTransaction.operations)
|
||||||
|
|
||||||
|
|
||||||
|
# Provides dynamic parameters for `set' command using config file
|
||||||
|
def update_commands():
|
||||||
|
global set
|
||||||
|
|
||||||
|
cfg = component.getUtility(IConfig)
|
||||||
|
avail = cfg.get('positions_available')
|
||||||
|
required = cfg.get('positions_required')
|
||||||
|
|
||||||
|
for pos in avail:
|
||||||
|
r = pos in required
|
||||||
|
set = click.option(f'--{pos}', metavar='USERNAME', required=r, prompt=r)(set)
|
|
@ -24,4 +24,7 @@ descriptions = {
|
||||||
'remove_user_from_auxiliary_groups': 'Remove user from auxiliary groups',
|
'remove_user_from_auxiliary_groups': 'Remove user from auxiliary groups',
|
||||||
'unsubscribe_user_from_auxiliary_mailing_lists': 'Unsubscribe user from auxiliary mailing lists',
|
'unsubscribe_user_from_auxiliary_mailing_lists': 'Unsubscribe user from auxiliary mailing lists',
|
||||||
'remove_sudo_role': 'Remove sudo role from LDAP',
|
'remove_sudo_role': 'Remove sudo role from LDAP',
|
||||||
|
'update_positions_ldap': 'Update positions in LDAP',
|
||||||
|
'update_exec_group_ldap': 'Update executive group in LDAP',
|
||||||
|
'subscribe_to_mailing_lists': 'Subscribe to mailing lists',
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,8 @@ def space_colon_kv(pairs: List[Tuple[str, str]]) -> List[str]:
|
||||||
key1000: val3
|
key1000: val3
|
||||||
val4
|
val4
|
||||||
"""
|
"""
|
||||||
|
if not pairs:
|
||||||
|
return []
|
||||||
lines = []
|
lines = []
|
||||||
maxlen = max(len(key) for key, val in pairs)
|
maxlen = max(len(key) for key, val in pairs)
|
||||||
for key, val in pairs:
|
for key, val in pairs:
|
||||||
|
|
|
@ -29,6 +29,12 @@ def update_positions():
|
||||||
required = cfg.get('positions_required')
|
required = cfg.get('positions_required')
|
||||||
available = cfg.get('positions_available')
|
available = cfg.get('positions_available')
|
||||||
|
|
||||||
|
# remove falsy values
|
||||||
|
body = {
|
||||||
|
positions: username for positions, username in body.items()
|
||||||
|
if username
|
||||||
|
}
|
||||||
|
|
||||||
for position in body.keys():
|
for position in body.keys():
|
||||||
if position not in available:
|
if position not in available:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
from click.testing import CliRunner
|
||||||
|
from ceo.cli import cli
|
||||||
|
|
||||||
|
|
||||||
|
def test_positions(cli_setup):
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
# Setup test data
|
||||||
|
for i in range(5):
|
||||||
|
runner.invoke(cli, ['members', 'add', f'test_{i}', '--cn', f'Test {i}', '--program', 'Math', '--terms', '1'], input='y\n')
|
||||||
|
runner.invoke(cli, ['groups', 'add', 'exec', '--description', 'Test Group'], input='y\n')
|
||||||
|
|
||||||
|
result = runner.invoke(cli, [
|
||||||
|
'positions', 'set',
|
||||||
|
'--president', 'test_0',
|
||||||
|
'--vice-president', 'test_1',
|
||||||
|
'--sysadmin', 'test_2',
|
||||||
|
'--secretary', 'test_3',
|
||||||
|
'--webmaster', 'test_4',
|
||||||
|
], input='y\n')
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output == '''
|
||||||
|
The positions will be updated:
|
||||||
|
president: test_0
|
||||||
|
vice-president: test_1
|
||||||
|
sysadmin: test_2
|
||||||
|
secretary: test_3
|
||||||
|
webmaster: test_4
|
||||||
|
treasurer:
|
||||||
|
cro:
|
||||||
|
librarian:
|
||||||
|
imapd:
|
||||||
|
offsck:
|
||||||
|
Do you want to continue? [y/N]: y
|
||||||
|
Update positions in LDAP... Done
|
||||||
|
Update executive group in LDAP... Done
|
||||||
|
Subscribe to mailing lists... Done
|
||||||
|
Transaction successfully completed.
|
||||||
|
'''[1:] # noqa: W291
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ['positions', 'get'])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert result.output == '''
|
||||||
|
president: test_0
|
||||||
|
secretary: test_3
|
||||||
|
sysadmin: test_2
|
||||||
|
vice-president: test_1
|
||||||
|
webmaster: test_4
|
||||||
|
'''[1:]
|
||||||
|
|
||||||
|
# Cleanup test data
|
||||||
|
for i in range(5):
|
||||||
|
runner.invoke(cli, ['members', 'delete', f'test_{i}'], input='y\n')
|
||||||
|
runner.invoke(cli, ['groups', 'delete', 'exec'], input='y\n')
|
|
@ -7,3 +7,8 @@ uw_domain = uwaterloo.internal
|
||||||
admin_host = phosphoric-acid
|
admin_host = phosphoric-acid
|
||||||
use_https = false
|
use_https = false
|
||||||
port = 9987
|
port = 9987
|
||||||
|
|
||||||
|
[positions]
|
||||||
|
required = president,vice-president,sysadmin
|
||||||
|
available = president,vice-president,treasurer,secretary,
|
||||||
|
sysadmin,cro,librarian,imapd,webmaster,offsck
|
||||||
|
|
|
@ -51,7 +51,7 @@ def cfg(_drone_hostname_mock):
|
||||||
with importlib.resources.path('tests', 'ceod_test_local.ini') as p:
|
with importlib.resources.path('tests', 'ceod_test_local.ini') as p:
|
||||||
config_file = p.__fspath__()
|
config_file = p.__fspath__()
|
||||||
_cfg = Config(config_file)
|
_cfg = Config(config_file)
|
||||||
component.provideUtility(_cfg, IConfig)
|
component.getGlobalSiteManager().registerUtility(_cfg, IConfig)
|
||||||
return _cfg
|
return _cfg
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ def krb_srv(cfg):
|
||||||
else:
|
else:
|
||||||
principal = 'ceod/' + socket.getfqdn()
|
principal = 'ceod/' + socket.getfqdn()
|
||||||
krb = KerberosService(principal)
|
krb = KerberosService(principal)
|
||||||
component.provideUtility(krb, IKerberosService)
|
component.getGlobalSiteManager().registerUtility(krb, IKerberosService)
|
||||||
|
|
||||||
delete_test_princs(krb)
|
delete_test_princs(krb)
|
||||||
yield krb
|
yield krb
|
||||||
|
@ -160,7 +160,7 @@ def ldap_srv_session(cfg, krb_srv, ldap_conn):
|
||||||
conn.add(base_dn, 'organizationalUnit')
|
conn.add(base_dn, 'organizationalUnit')
|
||||||
|
|
||||||
_ldap_srv = LDAPService()
|
_ldap_srv = LDAPService()
|
||||||
component.provideUtility(_ldap_srv, ILDAPService)
|
component.getGlobalSiteManager().registerUtility(_ldap_srv, ILDAPService)
|
||||||
|
|
||||||
yield _ldap_srv
|
yield _ldap_srv
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ def ldap_srv(ldap_srv_session, g_admin_ctx):
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def file_srv(cfg):
|
def file_srv(cfg):
|
||||||
_file_srv = FileService()
|
_file_srv = FileService()
|
||||||
component.provideUtility(_file_srv, IFileService)
|
component.getGlobalSiteManager().registerUtility(_file_srv, IFileService)
|
||||||
members_home = cfg.get('members_home')
|
members_home = cfg.get('members_home')
|
||||||
clubs_home = cfg.get('clubs_home')
|
clubs_home = cfg.get('clubs_home')
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ def file_srv(cfg):
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def http_client(cfg):
|
def http_client(cfg):
|
||||||
_client = HTTPClient()
|
_client = HTTPClient()
|
||||||
component.provideUtility(_client, IHTTPClient)
|
component.getGlobalSiteManager().registerUtility(_client, IHTTPClient)
|
||||||
return _client
|
return _client
|
||||||
|
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ def mock_mailman_server():
|
||||||
def mailman_srv(mock_mailman_server, cfg, http_client):
|
def mailman_srv(mock_mailman_server, cfg, http_client):
|
||||||
# TODO: test the RemoteMailmanService as well
|
# TODO: test the RemoteMailmanService as well
|
||||||
mailman = MailmanService()
|
mailman = MailmanService()
|
||||||
component.provideUtility(mailman, IMailmanService)
|
component.getGlobalSiteManager().registerUtility(mailman, IMailmanService)
|
||||||
return mailman
|
return mailman
|
||||||
|
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ def uwldap_srv(cfg, ldap_conn):
|
||||||
|
|
||||||
conn.add(base_dn, 'organizationalUnit')
|
conn.add(base_dn, 'organizationalUnit')
|
||||||
_uwldap_srv = UWLDAPService()
|
_uwldap_srv = UWLDAPService()
|
||||||
component.provideUtility(_uwldap_srv, IUWLDAPService)
|
component.getGlobalSiteManager().registerUtility(_uwldap_srv, IUWLDAPService)
|
||||||
yield _uwldap_srv
|
yield _uwldap_srv
|
||||||
|
|
||||||
delete_subtree(conn, base_dn)
|
delete_subtree(conn, base_dn)
|
||||||
|
@ -240,21 +240,21 @@ def mock_mail_server():
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def mail_srv(cfg, mock_mail_server):
|
def mail_srv(cfg, mock_mail_server):
|
||||||
_mail_srv = MailService()
|
_mail_srv = MailService()
|
||||||
component.provideUtility(_mail_srv, IMailService)
|
component.getGlobalSiteManager().registerUtility(_mail_srv, IMailService)
|
||||||
return _mail_srv
|
return _mail_srv
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def mysql_srv(cfg):
|
def mysql_srv(cfg):
|
||||||
mysql_srv = MySQLService()
|
mysql_srv = MySQLService()
|
||||||
component.provideUtility(mysql_srv, IDatabaseService, 'mysql')
|
component.getGlobalSiteManager().registerUtility(mysql_srv, IDatabaseService, 'mysql')
|
||||||
return mysql_srv
|
return mysql_srv
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def postgresql_srv(cfg):
|
def postgresql_srv(cfg):
|
||||||
psql_srv = PostgreSQLService()
|
psql_srv = PostgreSQLService()
|
||||||
component.provideUtility(psql_srv, IDatabaseService, 'postgresql')
|
component.getGlobalSiteManager().registerUtility(psql_srv, IDatabaseService, 'postgresql')
|
||||||
return psql_srv
|
return psql_srv
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue