-
Notifications
You must be signed in to change notification settings - Fork 31
Initial commit for multi-tenant-configuration #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b1ccec1
a597f70
0c803f6
f40ad0d
74bc708
cd8fc84
9717219
4617bc6
597260b
f26f524
2a23c78
417986f
947ee77
9c18544
1081112
97d9224
cd776b0
f94d37b
7c57358
3966713
c7abf80
078bea0
af8ecbe
9f2e64c
ef2e9ec
f66ba67
19bddc6
3a8f52e
d2bab49
ba009f5
69db473
cd17f0e
482917e
78dcbbe
193f1c6
c252098
2060de9
da377eb
7fbe1ee
5670ab6
5429368
2712647
265c01a
0987a53
fd83e52
4512e5a
9e1e6ce
b49fbbb
1c337e2
85766ec
9a8981f
a851a27
e971f73
f610d00
3659eb8
96e7636
2446129
c32d026
646f6e7
a3cf83d
7f7f517
82f3308
b044742
c1bdb2c
f4ca5ff
0e11c1a
cde69f7
0b00ec9
c360adf
f484f77
c314f95
009f96b
0753bdc
ca6e2fd
1bd47ef
140b244
39b2a19
4cab954
ddc70ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from collections import namedtuple | ||
|
||
BasicLogin = namedtuple('BasicLogin', ['user', 'password']) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
class Logger: | ||
""" | ||
minimal logger to either be verbose or don't print messages at all. | ||
""" | ||
verbose = True | ||
|
||
def __init__(self, verbose: bool): | ||
""" | ||
Constructor | ||
""" | ||
self.verbose = verbose | ||
|
||
def log(self, *args): | ||
if self.verbose: | ||
print(*args) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import yaml | ||
import json | ||
|
||
|
||
def read_yaml_file(path): | ||
""" | ||
reads a .yaml file and returns a dictionary | ||
:param path: path to the yaml file | ||
:return: returns a dictionary | ||
""" | ||
with open(path, 'r') as f: | ||
content = yaml.load(f, Loader=yaml.FullLoader) | ||
|
||
return content | ||
|
||
|
||
def create_yaml_file_from_json_file(json_file_path, yaml_file_path='test.yaml'): | ||
""" | ||
This function can be used to transform a json file to a yaml file. | ||
requires import json and import yaml | ||
:param json_file_path: path to json file | ||
:param yaml_file_path: path to yaml file (will be created if it does not exist) | ||
:return: | ||
""" | ||
|
||
with open(json_file_path, 'r') as json_file: | ||
json_data = json.load(json_file) | ||
with open(yaml_file_path, 'w') as file: | ||
yaml.dump(json_data, file, sort_keys=False) | ||
|
||
return True |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,22 +4,23 @@ | |
import os | ||
|
||
import requests | ||
from requests.auth import HTTPDigestAuth | ||
from requests.auth import HTTPDigestAuth, HTTPBasicAuth | ||
from requests_toolbelt import MultipartEncoder | ||
|
||
from rest_requests.request_error import RequestError | ||
|
||
|
||
def get_request(url, digest_login, element_description, asset_type_description=None, asset_description=None, | ||
stream=False): | ||
def get_request(url, login, element_description, asset_type_description=None, asset_description=None, | ||
stream=False, headers=None, use_digest=True): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
""" | ||
Make a get request to the given url with the given digest login. If the request fails with an error or a status | ||
code != 200, a Request Error with the error message /status code and the given descriptions is thrown. | ||
Make a get request to the given url with the given login credentials (Either Basic Auth or Digest Login). | ||
If the request fails with an error or a status code != 200, a Request Error with the error message /status code | ||
and the given descriptions is thrown. | ||
|
||
:param url: URL to make get request to | ||
:type url: str | ||
:param digest_login: The login credentials for digest authentication | ||
:type digest_login: DigestLogin | ||
:param login: The login credentials (either HTTP Basic or digest authentication) | ||
:type login: Login | ||
:param element_description: Element description in case of errors, e.g. 'event', 'series', 'tenants' | ||
:type element_description: str | ||
:param asset_type_description: Asset type description in case of errors, e.g. 'series', 'episode' | ||
|
@@ -28,13 +29,23 @@ def get_request(url, digest_login, element_description, asset_type_description=N | |
:type asset_description: str | ||
:param stream: Whether to stream response | ||
:type stream: bool | ||
:param headers: The headers to include in the request | ||
:type headers: dict | ||
:param use_digest: Whether to use digest login | ||
:type use_digest: bool | ||
:return: response | ||
:raise RequestError: | ||
""" | ||
|
||
headers = headers if headers else {} | ||
if use_digest: | ||
auth = HTTPDigestAuth(login.user, login.password) | ||
headers["X-Requested-Auth"] = "Digest" | ||
else: | ||
auth = HTTPBasicAuth(login.user, login.password) | ||
|
||
try: | ||
response = requests.get(url, auth=HTTPDigestAuth(digest_login.user, digest_login.password), | ||
headers={"X-Requested-Auth": "Digest"}, stream=stream) | ||
response = requests.get(url, auth=auth, headers=headers, stream=stream) | ||
except Exception as e: | ||
raise RequestError.with_error(url, str(e), element_description, asset_type_description, asset_description) | ||
|
||
|
@@ -129,3 +140,41 @@ def big_post_request(url, digest_login, element_description, asset_type_descript | |
raise RequestError.with_status_code(url, str(response.status_code), element_description, asset_type_description, | ||
asset_description) | ||
return response | ||
|
||
|
||
def put_request(url, digest_login, element_description, asset_type_description=None, asset_description=None, | ||
data=None, files=None): | ||
""" | ||
Make a put request to the given url with the given digest login. If the request fails with an error or a status | ||
code != 200, a Request Error with the error message /status code and the given descriptions is thrown. | ||
|
||
:param url: URL to make put request to | ||
:type url: str | ||
:param digest_login: The login credentials for digest authentication | ||
:type digest_login: DigestLogin | ||
:param element_description: Element description in case of errors, e.g. 'event', 'series', 'tenants' | ||
:type element_description: str | ||
:param asset_type_description: Asset type type description in case of errors, e.g. 'series', 'episode' | ||
:type asset_type_description: str | ||
:param asset_description: Asset description in case of errors, e.g. 'Dublin Core catalogs', 'ACL' | ||
:type asset_description: str | ||
:param data: Any data to attach to the request | ||
:type data: dict | ||
:param files: Any files to attach to the request | ||
:type files: dict | ||
:return: response | ||
:raise RequestError: | ||
""" | ||
|
||
auth = HTTPDigestAuth(digest_login.user, digest_login.password) | ||
headers = {"X-Requested-Auth": "Digest"} | ||
|
||
try: | ||
response = requests.put(url, auth=auth, headers=headers, data=data, files=files) | ||
except Exception as e: | ||
raise RequestError.with_error(url, str(e), element_description, asset_type_description, asset_description) | ||
|
||
if response.status_code < 200 or response.status_code > 299: | ||
raise RequestError.with_status_code(url, str(response.status_code), element_description, asset_type_description, | ||
asset_description) | ||
return response |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Multi-tenants User configuration scripts for Opencast | ||
|
||
This script simplifies the process of multi-tenant configuration. | ||
It allows to read in configurations of tenants and opencast organizations and checks if these configurations match the ones found on the respective opencast system. | ||
|
||
The *configuration* file `opencast-organizations.yml` in the environment folder contains specifications for: | ||
|
||
- Opencast Organizations | ||
- Switchcast System Accounts | ||
- Capture Agent Accounts | ||
- Tenants | ||
- Users (and their Roles) | ||
|
||
the *configuration* file `scripts/config/group_configuration.json` contains specifications for the Groups: | ||
- Group name | ||
- Group description | ||
- Tenant (on which this group should exist) | ||
- Group members | ||
|
||
## How to Use | ||
|
||
### Configuration | ||
|
||
The script is configured by editing the values in `config.py`: | ||
|
||
| Configuration Key | Description | Default/Example | | ||
| :-------------------- | :-------------------------------------------- | :--------------------------- | | ||
| `server_url` | The URL of the global admin node | `"http://localhost:8080"` | | ||
| `tenant_url_pattern` | The URL pattern of the target tenants | `"http://{}:8080"` | | ||
| `tenant_urls` | Optional dictionary of server URLs per tenant | `{'tenant1': 'http://tenant1:8080', 'tenant2': 'http://tenant2:8080'}` | | ||
| `ignored_tenants` | Optional list of tenants which are ignored | `['mh_default_org']` | | ||
| `digest_user` | The user name of the digest user | `opencast_system_account` | | ||
| `digest_pw` | The password of the digest user | `CHANGE_ME` | | ||
| `org_config_path` | The path to the organization config file | `"environment/{}/opencast-organizations.yml"` | | ||
| `group_config_path` | The path to the group config file | `"configurations/group_configuration.yaml"` | | ||
|
||
The configured digest user needs to exist on all tenants and has to have the same password. | ||
|
||
The optional dictionary `tenant_urls` can be used if the tenant-id is not an exact part of the tenant URL or the URLs don't follow a common pattern. | ||
|
||
#### group config: | ||
The group names in the group config file must be unique per Tenant! | ||
|
||
In the group description, python placeholder can be used (i.e. `{tenant_id}`) to include the current tenant-id in the description. | ||
|
||
### Usage | ||
|
||
The script can be called with the following command (all parameters in brackets are optional): | ||
|
||
`python main.py -e ENVIRONMENT [-t TENANT_ID] [-c CHECK] [-v True]` | ||
|
||
| Param | Description | | ||
| :---: | :---------- | | ||
| `-e` / `--environment` | The environment where to find the configuration file (either `staging` or `production`) | | ||
| `-t` / `--tenant-id` | The id of the target tenant to be configured | | ||
| `-c` / `--check` | checks to be performed (`users`, `groups`, `cast` or `capture`) (default: `all`) | | ||
| `-v` / `--verbose` | enables logging to be prompted if set to `True` | | ||
|
||
#### example: | ||
|
||
`python main.py -e staging -t tenant1 -c groups -v True` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
## Requirements | ||
|
||
This script was written for Python 3.8. You can install the necessary packages with | ||
|
||
**ToDo check the requirements file** | ||
|
||
`pip install -r requirements.txt` | ||
|
||
Additionally, this script uses modules contained in the _lib_ directory. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Configuration | ||
|
||
# Set this to your admin node | ||
server_url = "http://localhost:8080" | ||
|
||
# If you have multiple tenants, use an URL pattern. The blank {} will be filled with the tenant-id. | ||
# example: | ||
# tenant_url_pattern = "https://{}.example.org" | ||
tenant_url_pattern = "http://{}:8080" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe explain that the pattern will be filled with the tenant id. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
|
||
# You can also define a dictionary of tenant URLs, which will be prioritized over the URL pattern: | ||
# example: | ||
# tenant_urls = { | ||
# 'tenant1': 'http://tenant1:8080', | ||
# 'tenant2': 'http://tenant2:8080' | ||
# } | ||
|
||
# List of tenants which should be ignored | ||
ignored_tenants = [ | ||
'mh_default_org' | ||
] | ||
|
||
# Digest User login | ||
digest_user = "opencast_system_account" | ||
digest_pw = "CHANGE_ME" | ||
|
||
# path to environment configuration file. | ||
# The {} are a placeholder which will be filled with the environment passed as an argument (e.g. staging or production). | ||
org_config_path = "configurations/environment/{}/opencast-organizations.yml" | ||
# path to group configuration file | ||
group_config_path = "configurations/group_configuration.yaml" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
|
||
opencast_organizations: | ||
- id: All Tenants | ||
name: Dummy Tenant | ||
switchcast_system_accounts: | ||
- username: player | ||
name: Player System User | ||
email: [email protected] | ||
password: 34dchG6nbhmhnG | ||
roles: [ROLE_ADMIN, ROLE_SUDO] | ||
- username: annotate | ||
name: Annotate System User | ||
email: [email protected] | ||
password: jhvhuJH7utghfgfgJH | ||
roles: [ROLE_ADMIN, ROLE_SUDO] | ||
- username: cast | ||
name: Cast System User | ||
email: [email protected] | ||
password: jhvhuJH7utghfgfgJH | ||
roles: [ROLE_ADMIN, ROLE_SUDO] | ||
capture_agent_accounts: [] | ||
|
||
- id: tenant1 | ||
name: Tenant1 | ||
capture_agent_accounts: | ||
- username: ca-tenant1-ch | ||
password: jvblkajklvjhaklehr | ||
external_api_accounts: | ||
- username: moodle-tenant1-ch | ||
password: hghghjghdghdjd76 | ||
name: Moodle System User | ||
email: [email protected] | ||
roles: [ROLE_EXTERNAL_APPLICATION] | ||
- username: guy1 | ||
password: abc | ||
name: Guy 1 | ||
email: [email protected] | ||
roles: [ROLE_ADMIN] | ||
- username: guy2 | ||
password: abc | ||
name: Guy 2 | ||
email: [email protected] | ||
roles: [ROLE_ADMIN, ROLE_SUDO] | ||
- id: tenant2 | ||
name: Tenant2 | ||
capture_agent_accounts: | ||
- username: ca-tenant2-ch | ||
password: hjfkhfzuruzf76 | ||
external_api_accounts: | ||
- username: moodle-tenant2-ch | ||
password: 67rdghn | ||
name: Moodle System User | ||
email: [email protected] | ||
roles: [ROLE_EXTERNAL_APPLICATION] | ||
- username: guyx | ||
password: abc | ||
name: Guy X | ||
email: [email protected] | ||
roles: [ROLE_ADMIN, ROLE_SUDO] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pls also rename in doc string! Change type to something like
Login
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.