add CloudService
This commit is contained in:
parent
798510511f
commit
d7551eaf5c
|
@ -9,6 +9,7 @@ __pycache__/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
|
*.swp
|
||||||
.idea/
|
.idea/
|
||||||
/docs/*.1
|
/docs/*.1
|
||||||
/docs/*.5
|
/docs/*.5
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from zope.interface import Interface
|
||||||
|
|
||||||
|
from .IUser import IUser
|
||||||
|
|
||||||
|
|
||||||
|
class ICloudService(Interface):
|
||||||
|
"""Performs operations on the CSC Cloud."""
|
||||||
|
|
||||||
|
def create_account(user: IUser):
|
||||||
|
"""
|
||||||
|
Activate an LDAP account in CloudStack for the given user.
|
||||||
|
"""
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .ICloudService import ICloudService
|
||||||
from .IKerberosService import IKerberosService
|
from .IKerberosService import IKerberosService
|
||||||
from .IConfig import IConfig
|
from .IConfig import IConfig
|
||||||
from .IUser import IUser
|
from .IUser import IUser
|
||||||
|
|
|
@ -7,11 +7,12 @@ from zope import component
|
||||||
|
|
||||||
from .error_handlers import register_error_handlers
|
from .error_handlers import register_error_handlers
|
||||||
from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, IFileService, \
|
from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, IFileService, \
|
||||||
IMailmanService, IMailService, IUWLDAPService, IHTTPClient, IDatabaseService
|
IMailmanService, IMailService, IUWLDAPService, IHTTPClient, IDatabaseService, \
|
||||||
|
ICloudService
|
||||||
from ceo_common.model import Config, HTTPClient, RemoteMailmanService
|
from ceo_common.model import Config, HTTPClient, RemoteMailmanService
|
||||||
from ceod.api.spnego import init_spnego
|
from ceod.api.spnego import init_spnego
|
||||||
from ceod.model import KerberosService, LDAPService, FileService, \
|
from ceod.model import KerberosService, LDAPService, FileService, \
|
||||||
MailmanService, MailService, UWLDAPService
|
MailmanService, MailService, UWLDAPService, CloudService
|
||||||
from ceod.db import MySQLService, PostgreSQLService
|
from ceod.db import MySQLService, PostgreSQLService
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,3 +119,8 @@ def register_services(app):
|
||||||
if hostname == cfg.get('ceod_database_host'):
|
if hostname == cfg.get('ceod_database_host'):
|
||||||
psql_srv = PostgreSQLService()
|
psql_srv = PostgreSQLService()
|
||||||
component.provideUtility(psql_srv, IDatabaseService, 'postgresql')
|
component.provideUtility(psql_srv, IDatabaseService, 'postgresql')
|
||||||
|
|
||||||
|
# CloudService
|
||||||
|
if hostname == cfg.get('ceod_cloud_host'):
|
||||||
|
cloud_srv = CloudService()
|
||||||
|
component.provideUtility(cloud_srv, ICloudService)
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
from base64 import b64encode
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
from typing import Dict
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from zope import component
|
||||||
|
from zope.interface import implementer
|
||||||
|
|
||||||
|
from ceo_common.interfaces import ICloudService, IConfig, IUser
|
||||||
|
from ceo_common.model import Term
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(ICloudService)
|
||||||
|
class CloudService:
|
||||||
|
def __init__(self):
|
||||||
|
cfg = component.getUtility(IConfig)
|
||||||
|
self.api_key = cfg.get('cloudstack_api_key')
|
||||||
|
self.secret_key = cfg.get('cloudstack_secret_key')
|
||||||
|
self.base_url = cfg.get('cloudstack_base_url')
|
||||||
|
self.members_domain = cfg.get('cloudstack_members_domain')
|
||||||
|
|
||||||
|
def _create_url(self, params: Dict[str, str]) -> str:
|
||||||
|
# See https://docs.cloudstack.apache.org/en/latest/developersguide/dev.html#the-cloudstack-api
|
||||||
|
if 'apiKey' not in params and 'apikey' not in params:
|
||||||
|
params['apiKey'] = self.api_key
|
||||||
|
params['response'] = 'json'
|
||||||
|
request_str = '&'.join(
|
||||||
|
key + '=' + quote(val)
|
||||||
|
for key, val in params.items()
|
||||||
|
)
|
||||||
|
sig_str = '&'.join(
|
||||||
|
key.lower() + '=' + quote(val).lower()
|
||||||
|
for key, val in sorted(params.items())
|
||||||
|
)
|
||||||
|
sig = hmac.new(self.secret_key.encode(), sig_str.encode(), hashlib.sha1).digest()
|
||||||
|
encoded_sig = b64encode(sig).decode()
|
||||||
|
url = self.base_url + '?' + request_str + '&signature=' + quote(encoded_sig)
|
||||||
|
return url
|
||||||
|
|
||||||
|
def _get_domain_id(self, domain_name: str) -> str:
|
||||||
|
url = self._create_url({
|
||||||
|
'command': 'listDomains',
|
||||||
|
'details': 'min',
|
||||||
|
'name': domain_name,
|
||||||
|
})
|
||||||
|
resp = requests.get(url)
|
||||||
|
resp.raise_for_status()
|
||||||
|
d = resp.json()['listdomainsresponse']
|
||||||
|
assert d['count'] == 1, 'there should be one domain found'
|
||||||
|
return d['domain'][0]['id']
|
||||||
|
|
||||||
|
def create_account(self, user: IUser):
|
||||||
|
if not user.terms:
|
||||||
|
raise Exception('Only members may create cloud accounts')
|
||||||
|
most_recent_term = max(map(Term, user.terms))
|
||||||
|
if most_recent_term < Term.current():
|
||||||
|
raise Exception('Membership has expired for this user')
|
||||||
|
domain_id = self._get_domain_id(self.members_domain)
|
||||||
|
|
||||||
|
url = self._create_url({
|
||||||
|
'command': 'ldapCreateAccount',
|
||||||
|
'accounttype': '0',
|
||||||
|
'domainid': domain_id,
|
||||||
|
'username': user.uid,
|
||||||
|
})
|
||||||
|
resp = requests.post(url)
|
||||||
|
d = resp.json()['createaccountresponse']
|
||||||
|
if not resp.ok:
|
||||||
|
raise Exception(d['errortext'])
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .CloudService import CloudService
|
||||||
from .KerberosService import KerberosService
|
from .KerberosService import KerberosService
|
||||||
from .LDAPService import LDAPService, UserNotFoundError, GroupNotFoundError
|
from .LDAPService import LDAPService, UserNotFoundError, GroupNotFoundError
|
||||||
from .User import User
|
from .User import User
|
||||||
|
|
|
@ -10,6 +10,8 @@ fs_root_host = phosphoric-acid
|
||||||
database_host = caffeine
|
database_host = caffeine
|
||||||
# this is the host which can make API requests to Mailman
|
# this is the host which can make API requests to Mailman
|
||||||
mailman_host = mail
|
mailman_host = mail
|
||||||
|
# this is the host which is running a CloudStack management server
|
||||||
|
cloud_host = biloba
|
||||||
use_https = true
|
use_https = true
|
||||||
port = 9987
|
port = 9987
|
||||||
|
|
||||||
|
@ -72,3 +74,9 @@ host = localhost
|
||||||
username = REPLACE_ME
|
username = REPLACE_ME
|
||||||
password = REPLACE_ME
|
password = REPLACE_ME
|
||||||
host = localhost
|
host = localhost
|
||||||
|
|
||||||
|
[cloudstack]
|
||||||
|
api_key = REPLACE_ME
|
||||||
|
secret_key = REPLACE_ME
|
||||||
|
base_url = http://localhost:8080/api/client
|
||||||
|
members_domain = Members
|
||||||
|
|
|
@ -8,6 +8,7 @@ admin_host = phosphoric-acid
|
||||||
fs_root_host = phosphoric-acid
|
fs_root_host = phosphoric-acid
|
||||||
mailman_host = mail
|
mailman_host = mail
|
||||||
database_host = coffee
|
database_host = coffee
|
||||||
|
cloud_host = biloba
|
||||||
use_https = false
|
use_https = false
|
||||||
port = 9987
|
port = 9987
|
||||||
|
|
||||||
|
@ -67,3 +68,9 @@ host = localhost
|
||||||
username = postgres
|
username = postgres
|
||||||
password = postgres
|
password = postgres
|
||||||
host = localhost
|
host = localhost
|
||||||
|
|
||||||
|
[cloudstack]
|
||||||
|
api_key = REPLACE_ME
|
||||||
|
secret_key = REPLACE_ME
|
||||||
|
base_url = http://localhost:8080/api/client
|
||||||
|
members_domain = Members
|
||||||
|
|
Loading…
Reference in New Issue