Positions CLI #11
|
@ -13,6 +13,9 @@ from ceo_common.model import Config, HTTPClient
|
|||
|
||||
|
||||
def register_services():
|
||||
# Using base component directly so events get triggered
|
||||
baseComponent = component.getGlobalSiteManager()
|
||||
|
||||
# Config
|
||||
# This is a hack to determine if we're in the dev env or not
|
||||
if socket.getfqdn().endswith('.csclub.internal'):
|
||||
|
@ -21,11 +24,11 @@ def register_services():
|
|||
else:
|
||||
config_file = os.environ.get('CEO_CONFIG', '/etc/csc/ceo.ini')
|
||||
cfg = Config(config_file)
|
||||
component.provideUtility(cfg, IConfig)
|
||||
baseComponent.registerUtility(cfg, IConfig)
|
||||
|
||||
# HTTPService
|
||||
http_client = HTTPClient()
|
||||
component.provideUtility(http_client, IHTTPClient)
|
||||
baseComponent.registerUtility(http_client, IHTTPClient)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -2,6 +2,7 @@ import click
|
|||
|
||||
from .members import members
|
||||
from .groups import groups
|
||||
from .positions import positions
|
||||
from .updateprograms import updateprograms
|
||||
|
||||
|
||||
|
@ -12,4 +13,5 @@ def cli():
|
|||
|
||||
cli.add_command(members)
|
||||
cli.add_command(groups)
|
||||
cli.add_command(positions)
|
||||
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',
|
||||
'unsubscribe_user_from_auxiliary_mailing_lists': 'Unsubscribe user from auxiliary mailing lists',
|
||||
'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
|
||||
val4
|
||||
"""
|
||||
if not pairs:
|
||||
return []
|
||||
lines = []
|
||||
maxlen = max(len(key) for key, val in pairs)
|
||||
for key, val in pairs:
|
||||
|
|
|
@ -29,6 +29,12 @@ def update_positions():
|
|||
required = cfg.get('positions_required')
|
||||
available = cfg.get('positions_available')
|
||||
|
||||
# remove falsy values
|
||||
body = {
|
||||
positions: username for positions, username in body.items()
|
||||
if username
|
||||
}
|
||||
|
||||
for position in body.keys():
|
||||
if position not in available:
|
||||
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
|
||||
use_https = false
|
||||
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:
|
||||
config_file = p.__fspath__()
|
||||
_cfg = Config(config_file)
|
||||
component.provideUtility(_cfg, IConfig)
|
||||
component.getGlobalSiteManager().registerUtility(_cfg, IConfig)
|
||||
return _cfg
|
||||
|
||||
|
||||
|
@ -75,7 +75,7 @@ def krb_srv(cfg):
|
|||
else:
|
||||
principal = 'ceod/' + socket.getfqdn()
|
||||
krb = KerberosService(principal)
|
||||
component.provideUtility(krb, IKerberosService)
|
||||
component.getGlobalSiteManager().registerUtility(krb, IKerberosService)
|
||||
|
||||
delete_test_princs(krb)
|
||||
yield krb
|
||||
|
@ -160,7 +160,7 @@ def ldap_srv_session(cfg, krb_srv, ldap_conn):
|
|||
conn.add(base_dn, 'organizationalUnit')
|
||||
|
||||
_ldap_srv = LDAPService()
|
||||
component.provideUtility(_ldap_srv, ILDAPService)
|
||||
component.getGlobalSiteManager().registerUtility(_ldap_srv, ILDAPService)
|
||||
|
||||
yield _ldap_srv
|
||||
|
||||
|
@ -180,7 +180,7 @@ def ldap_srv(ldap_srv_session, g_admin_ctx):
|
|||
@pytest.fixture(scope='session')
|
||||
def file_srv(cfg):
|
||||
_file_srv = FileService()
|
||||
component.provideUtility(_file_srv, IFileService)
|
||||
component.getGlobalSiteManager().registerUtility(_file_srv, IFileService)
|
||||
members_home = cfg.get('members_home')
|
||||
clubs_home = cfg.get('clubs_home')
|
||||
|
||||
|
@ -194,7 +194,7 @@ def file_srv(cfg):
|
|||
@pytest.fixture(scope='session')
|
||||
def http_client(cfg):
|
||||
_client = HTTPClient()
|
||||
component.provideUtility(_client, IHTTPClient)
|
||||
component.getGlobalSiteManager().registerUtility(_client, IHTTPClient)
|
||||
return _client
|
||||
|
||||
|
||||
|
@ -210,7 +210,7 @@ def mock_mailman_server():
|
|||
def mailman_srv(mock_mailman_server, cfg, http_client):
|
||||
# TODO: test the RemoteMailmanService as well
|
||||
mailman = MailmanService()
|
||||
component.provideUtility(mailman, IMailmanService)
|
||||
component.getGlobalSiteManager().registerUtility(mailman, IMailmanService)
|
||||
return mailman
|
||||
|
||||
|
||||
|
@ -223,7 +223,7 @@ def uwldap_srv(cfg, ldap_conn):
|
|||
|
||||
conn.add(base_dn, 'organizationalUnit')
|
||||
_uwldap_srv = UWLDAPService()
|
||||
component.provideUtility(_uwldap_srv, IUWLDAPService)
|
||||
component.getGlobalSiteManager().registerUtility(_uwldap_srv, IUWLDAPService)
|
||||
yield _uwldap_srv
|
||||
|
||||
delete_subtree(conn, base_dn)
|
||||
|
@ -240,21 +240,21 @@ def mock_mail_server():
|
|||
@pytest.fixture(scope='session')
|
||||
def mail_srv(cfg, mock_mail_server):
|
||||
_mail_srv = MailService()
|
||||
component.provideUtility(_mail_srv, IMailService)
|
||||
component.getGlobalSiteManager().registerUtility(_mail_srv, IMailService)
|
||||
return _mail_srv
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def mysql_srv(cfg):
|
||||
mysql_srv = MySQLService()
|
||||
component.provideUtility(mysql_srv, IDatabaseService, 'mysql')
|
||||
component.getGlobalSiteManager().registerUtility(mysql_srv, IDatabaseService, 'mysql')
|
||||
return mysql_srv
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def postgresql_srv(cfg):
|
||||
psql_srv = PostgreSQLService()
|
||||
component.provideUtility(psql_srv, IDatabaseService, 'postgresql')
|
||||
component.getGlobalSiteManager().registerUtility(psql_srv, IDatabaseService, 'postgresql')
|
||||
return psql_srv
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
What is this doing?
zope's
registerUtility
emits an IUtilityRegistration containing the component that got registered. Here I'm just checking for that, and whether the registered component is an IConfig.The reason why this whole thing is here is to make the parameters (--president, --syscom, etc) follow what is in the config.