Browse Source

blackening

pull/5286/head
Devon Rueckner 3 years ago
parent
commit
7dadc107ce
  1. 173
      .buildkite/create_integration_testing_worksheet.py
  2. 147
      .buildkite/upload_artifacts.py
  3. 40
      build_tools/customize_build.py
  4. 22
      build_tools/customize_requirements.py
  5. 9
      build_tools/i18n/fonts.py
  6. 284
      build_tools/install_cexts.py
  7. 11
      build_tools/py2only.py
  8. 171
      docker/entrypoint.py
  9. 66
      docs/conf.py
  10. 6
      kolibri/__init__.py
  11. 1
      kolibri/__main__.py
  12. 1
      kolibri/core/analytics/__init__.py
  13. 14
      kolibri/core/analytics/api.py
  14. 12
      kolibri/core/analytics/api_urls.py
  15. 5
      kolibri/core/analytics/constants/nutrition_endpoints.py
  16. 85
      kolibri/core/analytics/management/commands/benchmark.py
  17. 62
      kolibri/core/analytics/management/commands/ping.py
  18. 84
      kolibri/core/analytics/management/commands/profile.py
  19. 83
      kolibri/core/analytics/measurements.py
  20. 82
      kolibri/core/analytics/middleware.py
  21. 8
      kolibri/core/analytics/pskolibri/__init__.py
  22. 64
      kolibri/core/analytics/pskolibri/_pslinux.py
  23. 86
      kolibri/core/analytics/pskolibri/_pswindows.py
  24. 8
      kolibri/core/analytics/pskolibri/common.py
  25. 7
      kolibri/core/analytics/serializers.py
  26. 94
      kolibri/core/analytics/test/test_api.py
  27. 10
      kolibri/core/analytics/test/test_ping.py
  28. 93
      kolibri/core/analytics/test/test_utils.py
  29. 102
      kolibri/core/analytics/utils.py
  30. 16
      kolibri/core/api.py
  31. 20
      kolibri/core/api_urls.py
  32. 9
      kolibri/core/apps.py
  33. 192
      kolibri/core/auth/api.py
  34. 22
      kolibri/core/auth/api_urls.py
  35. 6
      kolibri/core/auth/apps.py
  36. 7
      kolibri/core/auth/backends.py
  37. 12
      kolibri/core/auth/constants/facility_presets.py
  38. 14
      kolibri/core/auth/constants/user_kinds.py
  39. 122
      kolibri/core/auth/filters.py
  40. 31
      kolibri/core/auth/management/commands/deprovision.py
  41. 119
      kolibri/core/auth/management/commands/fullfacilitysync.py
  42. 102
      kolibri/core/auth/management/commands/importusers.py
  43. 31
      kolibri/core/auth/management/commands/syncdata.py
  44. 28
      kolibri/core/auth/management/commands/user_info.py
  45. 4
      kolibri/core/auth/middleware.py
  46. 454
      kolibri/core/auth/models.py
  47. 16
      kolibri/core/auth/permissions/auth.py
  48. 74
      kolibri/core/auth/permissions/base.py
  49. 4
      kolibri/core/auth/permissions/general.py
  50. 71
      kolibri/core/auth/serializers.py
  51. 4
      kolibri/core/auth/signals.py
  52. 64
      kolibri/core/auth/test/helpers.py
  53. 7
      kolibri/core/auth/test/migrationtestcase.py
  54. 76
      kolibri/core/auth/test/sync_utils.py
  55. 662
      kolibri/core/auth/test/test_api.py
  56. 27
      kolibri/core/auth/test/test_backend.py
  57. 45
      kolibri/core/auth/test/test_data_migrations.py
  58. 18
      kolibri/core/auth/test/test_datasets.py
  59. 36
      kolibri/core/auth/test/test_deprovisioning.py
  60. 1
      kolibri/core/auth/test/test_middleware.py
  61. 358
      kolibri/core/auth/test/test_models.py
  62. 351
      kolibri/core/auth/test/test_morango_integration.py
  63. 593
      kolibri/core/auth/test/test_permissions.py
  64. 68
      kolibri/core/auth/test/test_permissions_classes.py
  65. 74
      kolibri/core/auth/test/test_roles_and_membership.py
  66. 115
      kolibri/core/auth/test/test_user_import.py
  67. 2
      kolibri/core/auth/test/test_users.py
  68. 2
      kolibri/core/content/__init__.py
  69. 414
      kolibri/core/content/api.py
  70. 34
      kolibri/core/content/api_urls.py
  71. 7
      kolibri/core/content/apps.py
  72. 30
      kolibri/core/content/hooks.py
  73. 1
      kolibri/core/content/legacy_models.py
  74. 83
      kolibri/core/content/management/commands/content.py
  75. 20
      kolibri/core/content/management/commands/deletechannel.py
  76. 29
      kolibri/core/content/management/commands/exportchannel.py
  77. 25
      kolibri/core/content/management/commands/exportcontent.py
  78. 32
      kolibri/core/content/management/commands/generate_schema.py
  79. 74
      kolibri/core/content/management/commands/importchannel.py
  80. 127
      kolibri/core/content/management/commands/importcontent.py
  81. 13
      kolibri/core/content/management/commands/listchannels.py
  82. 9
      kolibri/core/content/management/commands/scanforcontent.py
  83. 27
      kolibri/core/content/management/commands/setchannelposition.py
  84. 126
      kolibri/core/content/models.py
  85. 4
      kolibri/core/content/permissions.py
  86. 384
      kolibri/core/content/serializers.py
  87. 10
      kolibri/core/content/signals.py
  88. 14
      kolibri/core/content/test/sqlalchemytesting.py
  89. 296
      kolibri/core/content/test/test_annotation.py
  90. 337
      kolibri/core/content/test/test_channel_import.py
  91. 36
      kolibri/core/content/test/test_channel_order.py
  92. 1052
      kolibri/core/content/test/test_content_app.py
  93. 145
      kolibri/core/content/test/test_data_migrations.py
  94. 47
      kolibri/core/content/test/test_deletechannel.py
  95. 37
      kolibri/core/content/test/test_downloadcontent.py
  96. 639
      kolibri/core/content/test/test_import_export.py
  97. 176
      kolibri/core/content/test/test_movedirectory.py
  98. 339
      kolibri/core/content/test/test_sqlalchemy_bridge.py
  99. 127
      kolibri/core/content/test/test_zipcontent.py
  100. 14
      kolibri/core/content/urls.py

173
.buildkite/create_integration_testing_worksheet.py

@ -22,7 +22,7 @@ SPREADSHEET_TITLE = "Integration testing with Gherkin scenarios"
# Use to get the Kolibri version, for the integration testing spreadsheet
SHEET_TAG = os.getenv("BUILDKITE_TAG")
SHEET_TPL_COLUMN = 'B'
SHEET_TPL_COLUMN = "B"
SHEET_TPL_START_VALUE = 5
SHEET_INDEX = 0
@ -45,14 +45,19 @@ if SPREADSHEET_CREDENTIALS == "" or SPREADSHEET_CREDENTIALS is None:
logging.info("Spreadsheet credentials not exist")
sys.exit()
GIT_FEATURE_LINK = "https://github.com/learningequality/kolibri/blob/%s/integration_testing/features" \
GIT_FEATURE_LINK = (
"https://github.com/learningequality/kolibri/blob/%s/integration_testing/features"
% (SHEET_TAG)
)
SCOPE = ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive',
]
SCOPE = [
"https://spreadsheets.google.com/feeds",
"https://www.googleapis.com/auth/drive",
]
G_ACCESS = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name(SPREADSHEET_CREDENTIALS, SCOPE))
G_ACCESS = gspread.authorize(
ServiceAccountCredentials.from_json_keyfile_name(SPREADSHEET_CREDENTIALS, SCOPE)
)
def get_feature_name(str_arg):
@ -78,7 +83,7 @@ def get_worksheet_link(sheet_id, wks_id):
sheet_domain = "https://docs.google.com/spreadsheets/d/"
link_arg = "{0}{1}{2}{3}"
final_link = link_arg.format(sheet_domain, sheet_id, "/edit#gid=", wks_id)
logging.info('Here is the new integration testing worksheet %s' % final_link)
logging.info("Here is the new integration testing worksheet %s" % final_link)
return final_link
@ -97,8 +102,8 @@ def check_name(str_arg):
def create_artifact(str_arg):
buidkite_path = get_kolibri_path() + "/.buildkite/"
txt_path = buidkite_path + 'spreadsheet-link.txt'
file = open(txt_path, 'w')
txt_path = buidkite_path + "spreadsheet-link.txt"
file = open(txt_path, "w")
file.write(str_arg)
file.close()
@ -115,14 +120,14 @@ def fetch_feature_files():
sheet_contents = []
sheet_cell_count = []
counter = 0
with open(order_txt_path, 'r') as read_txt:
with open(order_txt_path, "r") as read_txt:
txt_lines = read_txt.readlines()
for line in txt_lines:
file_name = line.strip()
role_path = feature_dir + file_name + "/"
order_txt_path = role_path + order_name
if check_name(file_name):
with open(order_txt_path, 'r') as read_feature:
with open(order_txt_path, "r") as read_feature:
feature_lines = read_feature.readlines()
role_cell_name = get_role_name(file_name)
@ -134,10 +139,12 @@ def fetch_feature_files():
feature_path = role_path + feature_file_name
if check_name(feature_file_name):
feature_name = get_feature_name(feature_file_name)
cell_val = '=HYPERLINK("%s/%s/%s","%s")' % (GIT_FEATURE_LINK,
file_name,
feature_file_name,
feature_name)
cell_val = '=HYPERLINK("%s/%s/%s","%s")' % (
GIT_FEATURE_LINK,
file_name,
feature_file_name,
feature_name,
)
if not check_file_exist(feature_path):
cell_val = feature_name
sheet_contents.append(cell_val)
@ -155,17 +162,18 @@ def sheet_insert_rows(sheet, wrk_sheet, start_index=0, end_index=0):
I reuse the gspread function to make this API request.
"""
body = {
"requests": [{
"insertDimension": {
"range":
{
"sheetId": wrk_sheet.id,
"dimension": "ROWS",
"startIndex": start_index,
"endIndex": end_index
"requests": [
{
"insertDimension": {
"range": {
"sheetId": wrk_sheet.id,
"dimension": "ROWS",
"startIndex": start_index,
"endIndex": end_index,
}
}
}
}]
]
}
sheet.batch_update(body)
@ -176,15 +184,14 @@ def rename_worksheet(sheet, wrk_sheet, sheet_name):
I reuse the gspread function to make this API request.
"""
body = {
"requests": [{
"updateSheetProperties": {
"properties": {
"sheetId": wrk_sheet.id,
"title": SHEET_TAG,
},
"fields": "title",
"requests": [
{
"updateSheetProperties": {
"properties": {"sheetId": wrk_sheet.id, "title": SHEET_TAG},
"fields": "title",
}
}
}]
]
}
sheet.batch_update(body)
@ -196,25 +203,23 @@ def search_file_name(file_name):
"""
self = G_ACCESS
files = []
page_token = ''
page_token = ""
url = "https://www.googleapis.com/drive/v3/files"
search = "name='{0}'".format(
file_name
)
search = "name='{0}'".format(file_name)
params = {
'q': search,
"q": search,
"pageSize": 1000,
'supportsTeamDrives': True,
'includeTeamDriveItems': True,
"supportsTeamDrives": True,
"includeTeamDriveItems": True,
}
while page_token is not None:
if page_token:
params['pageToken'] = page_token
params["pageToken"] = page_token
res = self.request('get', url, params=params).json()
files.extend(res['files'])
page_token = res.get('nextPageToken', None)
res = self.request("get", url, params=params).json()
files.extend(res["files"])
page_token = res.get("nextPageToken", None)
return files
@ -225,20 +230,12 @@ def create_sheet_container(file_id, dir_name):
"""
self = G_ACCESS
payload = {
'title': dir_name,
'mimeType': 'application/vnd.google-apps.folder',
"parents":
[{
"kind": "drive#childList",
"id": file_id,
}]
"title": dir_name,
"mimeType": "application/vnd.google-apps.folder",
"parents": [{"kind": "drive#childList", "id": file_id}],
}
r = self.request(
'post',
DRIVE_FILES_API_V2_URL,
json=payload
)
return r.json()['id']
r = self.request("post", DRIVE_FILES_API_V2_URL, json=payload)
return r.json()["id"]
def sheet_copy(file_id, dist_id, title=None, copy_permissions=False):
@ -248,26 +245,15 @@ def sheet_copy(file_id, dist_id, title=None, copy_permissions=False):
"""
self = G_ACCESS
url = '{0}/{1}/copy'.format(
DRIVE_FILES_API_V2_URL,
file_id
)
url = "{0}/{1}/copy".format(DRIVE_FILES_API_V2_URL, file_id)
payload = {
'title': title,
'mimeType': 'application/vnd.google-apps.spreadsheet',
"parents":
[{
"kind": "drive#childList",
"id": dist_id,
}]
"title": title,
"mimeType": "application/vnd.google-apps.spreadsheet",
"parents": [{"kind": "drive#childList", "id": dist_id}],
}
r = self.request(
'post',
url,
json=payload
)
spreadsheet_id = r.json()['id']
r = self.request("post", url, json=payload)
spreadsheet_id = r.json()["id"]
new_spreadsheet = self.open_by_key(spreadsheet_id)
@ -276,14 +262,14 @@ def sheet_copy(file_id, dist_id, title=None, copy_permissions=False):
permissions = original.list_permissions()
for p in permissions:
if p.get('deleted'):
if p.get("deleted"):
continue
try:
new_spreadsheet.share(
value=p['emailAddress'],
perm_type=p['type'],
role=p['role'],
notify=False
value=p["emailAddress"],
perm_type=p["type"],
role=p["role"],
notify=False,
)
except Exception:
pass
@ -299,12 +285,16 @@ def sheet_container():
dist_folder = SHEET_PARENT_CONTAINER_ID
dir_id = create_sheet_container(dist_folder, SHEET_TAG)
return dir_id
return drive_search[0]['id']
return drive_search[0]["id"]
def create_spreadsheet():
spreadsheet = sheet_copy(SPREADSHEET_TPL_KEY, sheet_container(),
title=SPREADSHEET_TITLE, copy_permissions=True)
spreadsheet = sheet_copy(
SPREADSHEET_TPL_KEY,
sheet_container(),
title=SPREADSHEET_TITLE,
copy_permissions=True,
)
worksheet = spreadsheet.get_worksheet(SHEET_INDEX)
feature_cells, feature_contents = fetch_feature_files()
@ -323,23 +313,32 @@ def create_spreadsheet():
end_index_counter = end_index + 3
role_counter += 1
cell_range = SHEET_TPL_COLUMN + str(SHEET_TPL_START_VALUE) \
+ ":" + SHEET_TPL_COLUMN + str(SHEET_TPL_START_VALUE + len(feature_contents))
cell_range = (
SHEET_TPL_COLUMN
+ str(SHEET_TPL_START_VALUE)
+ ":"
+ SHEET_TPL_COLUMN
+ str(SHEET_TPL_START_VALUE + len(feature_contents))
)
cell_list = worksheet.range(cell_range)
cell_counter = 0
for cell in cell_list:
try:
feature_val = feature_contents[cell_counter]
if not feature_val == CELL_VALUE_SEPARATOR:
cell.value = feature_val
cell.value = feature_val
cell_counter += 1
except Exception:
pass
# Insert all the feature scenarios at the spreadsheet.
worksheet.update_cells(cell_list, 'USER_ENTERED')
worksheet.update_cells(cell_list, "USER_ENTERED")
template_name = SHEET_TAG + " base template"
spreadsheet.duplicate_sheet(worksheet.id, insert_sheet_index=None, new_sheet_id=None,
new_sheet_name=template_name)
spreadsheet.duplicate_sheet(
worksheet.id,
insert_sheet_index=None,
new_sheet_id=None,
new_sheet_name=template_name,
)
rename_worksheet(spreadsheet, worksheet, SHEET_TAG)
sheet_link = get_worksheet_link(spreadsheet.id, worksheet.id)
create_artifact(sheet_link)

147
.buildkite/upload_artifacts.py

@ -34,55 +34,55 @@ BUILD_ID = os.getenv("BUILDKITE_BUILD_NUMBER")
TAG = os.getenv("BUILDKITE_TAG")
COMMIT = os.getenv("BUILDKITE_COMMIT")
RELEASE_DIR = 'release'
RELEASE_DIR = "release"
PROJECT_PATH = os.path.join(os.getcwd())
# Python packages artifact location
DIST_DIR = os.path.join(PROJECT_PATH, "dist")
headers = {'Authorization': 'token %s' % ACCESS_TOKEN}
headers = {"Authorization": "token %s" % ACCESS_TOKEN}
INSTALLER_CAT = 'Installers'
INSTALLER_CAT = "Installers"
PYTHON_PKG_CAT = 'Python packages'
PYTHON_PKG_CAT = "Python packages"
# Manifest of files, keyed by extension
file_manifest = {
'deb': {
'extension': 'deb',
'description': 'Debian Package',
'category': INSTALLER_CAT,
'content_type': 'application/vnd.debian.binary-package',
"deb": {
"extension": "deb",
"description": "Debian Package",
"category": INSTALLER_CAT,
"content_type": "application/vnd.debian.binary-package",
},
'unsigned-exe': {
'extension': 'exe',
'description': 'Unsigned Windows installer',
'category': INSTALLER_CAT,
'content_type': 'application/x-ms-dos-executable',
"unsigned-exe": {
"extension": "exe",
"description": "Unsigned Windows installer",
"category": INSTALLER_CAT,
"content_type": "application/x-ms-dos-executable",
},
'signed-exe': {
'extension': 'exe',
'description': 'Signed Windows installer',
'category': INSTALLER_CAT,
'content_type': 'application/x-ms-dos-executable',
"signed-exe": {
"extension": "exe",
"description": "Signed Windows installer",
"category": INSTALLER_CAT,
"content_type": "application/x-ms-dos-executable",
},
'pex': {
'extension': 'pex',
'description': 'Pex file',
'category': PYTHON_PKG_CAT,
'content_type': 'application/octet-stream',
"pex": {
"extension": "pex",
"description": "Pex file",
"category": PYTHON_PKG_CAT,
"content_type": "application/octet-stream",
},
'whl': {
'extension': 'whl',
'description': 'Whl file',
'category': PYTHON_PKG_CAT,
'content_type': 'application/zip',
"whl": {
"extension": "whl",
"description": "Whl file",
"category": PYTHON_PKG_CAT,
"content_type": "application/zip",
},
'gz': {
'extension': 'gz',
'description': 'Tar file',
'category': PYTHON_PKG_CAT,
'content_type': 'application/gzip',
"gz": {
"extension": "gz",
"description": "Tar file",
"category": PYTHON_PKG_CAT,
"content_type": "application/gzip",
},
# 'apk': {
# 'extension': 'apk',
@ -93,13 +93,13 @@ file_manifest = {
}
file_order = [
'deb',
'unsigned-exe',
'signed-exe',
"deb",
"unsigned-exe",
"signed-exe",
# 'apk',
'pex',
'whl',
'gz',
"pex",
"whl",
"gz",
]
gh = login(token=ACCESS_TOKEN)
@ -115,8 +115,8 @@ def create_status_report_html(artifacts):
for ext in file_order:
if ext in artifacts:
artifact = artifacts[ext]
if artifact['category'] != current_heading:
current_heading = artifact['category']
if artifact["category"] != current_heading:
current_heading = artifact["category"]
html += "<h2>{heading}</h2>\n".format(heading=current_heading)
html += "<p>{description}: <a href='{media_url}'>{name}</a></p>\n".format(
**artifact
@ -135,12 +135,12 @@ def create_github_status(report_url):
"success",
target_url=report_url,
description="Kolibri Buildkite assets",
context="buildkite/kolibri/assets"
context="buildkite/kolibri/assets",
)
if status:
logging.info('Successfully created Github status for commit %s.' % COMMIT)
logging.info("Successfully created Github status for commit %s." % COMMIT)
else:
logging.info('Error encounter. Now exiting!')
logging.info("Error encounter. Now exiting!")
sys.exit(1)
@ -152,7 +152,7 @@ def collect_local_artifacts():
artifacts_dict = {}
def create_exe_data(filename, data):
data_name = '-unsigned'
data_name = "-unsigned"
if "-signed" in filename:
data_name = "-signed"
data_name_exe = data_name[1:] + "-exe"
@ -165,8 +165,10 @@ def collect_local_artifacts():
# Remove leading '.'
# print("...>", artifact, "<......")
file_extension = file_extension[1:]
data = {"name": artifact,
"file_location": "%s/%s" % (artifact_dir, artifact)}
data = {
"name": artifact,
"file_location": "%s/%s" % (artifact_dir, artifact),
}
if file_extension == "exe":
create_exe_data(filename, data)
@ -174,6 +176,7 @@ def collect_local_artifacts():
data.update(file_manifest[file_extension])
logging.info("Collect file data: (%s)" % data)
artifacts_dict[file_extension] = data
create_artifact_data(DIST_DIR)
return artifacts_dict
@ -190,23 +193,28 @@ def upload_artifacts():
for file_data in artifacts.values():
logging.info("Uploading file (%s)" % (file_data.get("name")))
if is_release:
blob = bucket.blob('kolibri-%s-%s-%s' % (RELEASE_DIR, BUILD_ID, file_data.get("name")))
blob = bucket.blob(
"kolibri-%s-%s-%s" % (RELEASE_DIR, BUILD_ID, file_data.get("name"))
)
else:
blob = bucket.blob('kolibri-buildkite-build-%s-%s-%s' % (ISSUE_ID, BUILD_ID, file_data.get("name")))
blob = bucket.blob(
"kolibri-buildkite-build-%s-%s-%s"
% (ISSUE_ID, BUILD_ID, file_data.get("name"))
)
blob.upload_from_filename(filename=file_data.get("file_location"))
blob.make_public()
file_data.update({'media_url': blob.media_link})
file_data.update({"media_url": blob.media_link})
html = create_status_report_html(artifacts)
# add count to report html to avoid duplicate.
report_count = BUILD_ID + "-first"
if 'signed-exe' in artifacts:
if "signed-exe" in artifacts:
report_count = BUILD_ID + "-second"
blob = bucket.blob('kolibri-%s-%s-report.html' % (RELEASE_DIR, report_count))
blob = bucket.blob("kolibri-%s-%s-report.html" % (RELEASE_DIR, report_count))
blob.upload_from_string(html, content_type='text/html')
blob.upload_from_string(html, content_type="text/html")
blob.make_public()
@ -215,15 +223,15 @@ def upload_artifacts():
if TAG:
# Building from a tag, this is probably a release!
# Have to do this with requests because github3 does not support this interface yet
get_release_asset_url = requests.get("https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag}".format(
owner=REPO_OWNER,
repo=REPO_NAME,
tag=TAG,
))
get_release_asset_url = requests.get(
"https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag}".format(
owner=REPO_OWNER, repo=REPO_NAME, tag=TAG
)
)
if get_release_asset_url.status_code == 200:
# Definitely a release!
release_id = get_release_asset_url.json()['id']
release_name = get_release_asset_url.json()['name']
release_id = get_release_asset_url.json()["id"]
release_name = get_release_asset_url.json()["name"]
release = repository.release(id=release_id)
logging.info("Uploading built assets to Github Release: %s" % release_name)
for file_extension in file_order:
@ -232,16 +240,21 @@ def upload_artifacts():
logging.info("Uploading release asset: %s" % (artifact.get("name")))
# For some reason github3 does not let us set a label at initial upload
asset = release.upload_asset(
content_type=artifact['content_type'],
name=artifact['name'],
asset=open(artifact['file_location'], 'rb')
content_type=artifact["content_type"],
name=artifact["name"],
asset=open(artifact["file_location"], "rb"),
)
if asset:
# So do it after the initial upload instead
asset.edit(artifact['name'], label=artifact['description'])
logging.info("Successfully uploaded release asset: %s" % (artifact.get('name')))
asset.edit(artifact["name"], label=artifact["description"])
logging.info(
"Successfully uploaded release asset: %s"
% (artifact.get("name"))
)
else:
logging.error("Error uploading release asset: %s" % (artifact.get('name')))
logging.error(
"Error uploading release asset: %s" % (artifact.get("name"))
)
def main():

40
build_tools/customize_build.py

@ -11,10 +11,11 @@ import tempfile
import requests
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), "..")))
os.environ.setdefault(
"RUN_TIME_PLUGINS", os.path.realpath(os.path.join(os.path.dirname(__file__), "default_plugins.txt"))
"RUN_TIME_PLUGINS",
os.path.realpath(os.path.join(os.path.dirname(__file__), "default_plugins.txt")),
)
plugins_cache = {}
@ -24,29 +25,44 @@ def load_plugins_from_file(file_path):
global plugins_cache
if file_path not in plugins_cache:
# We have been passed a URL, not a local file path
if file_path.startswith('http'):
print("Downloading plugins manifest from {file_path}".format(file_path=file_path))
if file_path.startswith("http"):
print(
"Downloading plugins manifest from {file_path}".format(
file_path=file_path
)
)
_, path = tempfile.mkstemp(suffix=".txt", text=True)
with open(path, 'w') as f:
with open(path, "w") as f:
r = requests.get(file_path)
f.write(r.content)
file_path = path
with open(file_path, 'r') as f:
plugins_cache[file_path] = [plugin.strip() for plugin in f.readlines() if plugin.strip()]
with open(file_path, "r") as f:
plugins_cache[file_path] = [
plugin.strip() for plugin in f.readlines() if plugin.strip()
]
return plugins_cache[file_path]
build_config_path = os.path.join(os.path.dirname(__file__), "../kolibri/utils/build_config")
build_config_path = os.path.join(
os.path.dirname(__file__), "../kolibri/utils/build_config"
)
default_settings_template = "settings_path = '{path}'"
def set_default_settings_module():
if "DEFAULT_SETTINGS_MODULE" in os.environ and os.environ["DEFAULT_SETTINGS_MODULE"]:
if (
"DEFAULT_SETTINGS_MODULE" in os.environ
and os.environ["DEFAULT_SETTINGS_MODULE"]
):
default_settings_path = os.environ["DEFAULT_SETTINGS_MODULE"]
with open(os.path.join(build_config_path, "default_settings.py"), 'w') as f:
with open(os.path.join(build_config_path, "default_settings.py"), "w") as f:
# Just write out settings_path = '<settings_path>'
print("Setting default settings module to {path}".format(path=default_settings_path))
print(
"Setting default settings module to {path}".format(
path=default_settings_path
)
)
f.write(default_settings_template.format(path=default_settings_path))
@ -56,7 +72,7 @@ run_time_plugin_template = "plugins = {plugins}\n"
def set_run_time_plugins():
if "RUN_TIME_PLUGINS" in os.environ and os.environ["RUN_TIME_PLUGINS"]:
runtime_plugins = load_plugins_from_file(os.environ["RUN_TIME_PLUGINS"])
with open(os.path.join(build_config_path, "default_plugins.py"), 'w') as f:
with open(os.path.join(build_config_path, "default_plugins.py"), "w") as f:
# Just write out 'plugins = [...]' <-- list of plugins
print("Setting run time plugins to:")
for runtime_plugin in runtime_plugins:

22
build_tools/customize_requirements.py

@ -14,18 +14,28 @@ def add_requirements_to_base():
if "EXTRA_REQUIREMENTS" in os.environ and os.environ["EXTRA_REQUIREMENTS"]:
file_path = os.environ["EXTRA_REQUIREMENTS"]
# We have been passed a URL, not a local file path
if file_path.startswith('http'):
print("Downloading extra requirements from {file_path}".format(file_path=file_path))
if file_path.startswith("http"):
print(
"Downloading extra requirements from {file_path}".format(
file_path=file_path
)
)
_, path = tempfile.mkstemp(suffix=".txt", text=True)
with open(path, 'w') as f:
with open(path, "w") as f:
r = requests.get(file_path)
f.write(r.content)
file_path = path
try:
with open(file_path, 'r') as f:
requirements = [requirement.strip() for requirement in f.readlines() if requirement.strip()]
with open(file_path, "r") as f:
requirements = [
requirement.strip()
for requirement in f.readlines()
if requirement.strip()
]
if requirements:
with open(os.path.join(os.path.dirname(__file__), '../requirements.txt'), 'a') as f:
with open(
os.path.join(os.path.dirname(__file__), "../requirements.txt"), "a"
) as f:
f.writelines(requirements)
except IOError:
pass

9
build_tools/i18n/fonts.py

@ -90,7 +90,14 @@ def _woff_font_path(name, is_bold):
def _load_font(path):
guess = mimetypes.guess_type(path)
if guess[0] not in ["font/ttc", "font/ttf", "font/otf", "font/woff", "application/font-sfnt", "application/font-woff"]:
if guess[0] not in [
"font/ttc",
"font/ttf",
"font/otf",
"font/woff",
"application/font-sfnt",
"application/font-woff",
]:
logging.error("Not a font file: {}".format(path))
logging.error("Guessed mimetype: '{}'".format(guess[0]))
logging.error("If this is a text file: do you have Git LFS installed?")

284
build_tools/install_cexts.py

@ -9,9 +9,13 @@ import requests
from bs4 import BeautifulSoup
DIST_CEXT = os.path.join(
os.path.dirname(os.path.realpath(os.path.dirname(__file__))), 'kolibri', 'dist', 'cext')
PYPI_DOWNLOAD = 'https://pypi.python.org/simple/'
PIWHEEL_DOWNLOAD = 'https://www.piwheels.hostedpi.com/simple/'
os.path.dirname(os.path.realpath(os.path.dirname(__file__))),
"kolibri",
"dist",
"cext",
)
PYPI_DOWNLOAD = "https://pypi.python.org/simple/"
PIWHEEL_DOWNLOAD = "https://www.piwheels.hostedpi.com/simple/"
def get_path_with_arch(platform, path, abi, implementation, python_version):
@ -21,84 +25,174 @@ def get_path_with_arch(platform, path, abi, implementation, python_version):
# Split the platform into two parts.
# For example: manylinux1_x86_64 to Linux, x86_64
platform_split = platform.replace('manylinux1', 'Linux').split('_', 1)
platform_split = platform.replace("manylinux1", "Linux").split("_", 1)
# Windows 32-bit's machine name is x86.
if platform_split[0] == 'win32':
return os.path.join(path, 'Windows', 'x86')
if platform_split[0] == "win32":
return os.path.join(path, "Windows", "x86")
# Windows 64-bit
elif platform_split[0] == 'win':
return os.path.join(path, 'Windows', 'AMD64')
elif platform_split[0] == "win":
return os.path.join(path, "Windows", "AMD64")
# Prior to CPython 3.3, there were two ABI-incompatible ways of building CPython
# There could be abi tag 'm' for narrow-unicode and abi tag 'mu' for wide-unicode
if implementation == 'cp' and int(python_version) < 33:
if implementation == "cp" and int(python_version) < 33:
return os.path.join(path, platform_split[0], abi, platform_split[1])
return os.path.join(path, platform_split[0], platform_split[1])
def download_package(path, platform, version, implementation, abi, name, pk_version, index_url, filename):
def download_package(
path, platform, version, implementation, abi, name, pk_version, index_url, filename
):
"""
Download the package according to platform, python version, implementation and abi.
"""
if abi == 'abi3':
if abi == "abi3":
return_code = download_package_abi3(
path, platform, version, implementation, abi, name, pk_version, index_url, filename)
path,
platform,
version,
implementation,
abi,
name,
pk_version,
index_url,
filename,
)
else:
return_code = subprocess.call([
'python', 'kolibripip.pex', 'download', '-q', '-d', path, '--platform', platform,
'--python-version', version, '--implementation', implementation,
'--abi', abi, '-i', index_url, '{}=={}'.format(name, pk_version)
])
return_code = subprocess.call(
[
"python",
"kolibripip.pex",
"download",
"-q",
"-d",
path,
"--platform",
platform,
"--python-version",
version,
"--implementation",
implementation,
"--abi",
abi,
"-i",
index_url,
"{}=={}".format(name, pk_version),
]
)
# When downloaded as a tar.gz, convert to a wheel file first.
# This is specifically for pycparser package.
files = os.listdir(path)
for file in files:
if file.endswith('tar.gz'):
subprocess.call([
'python', 'kolibripip.pex', 'wheel', '-q', '-w',
path, os.path.join(path, file), '--no-deps'])
if file.endswith("tar.gz"):
subprocess.call(
[
"python",
"kolibripip.pex",
"wheel",
"-q",
"-w",
path,
os.path.join(path, file),
"--no-deps",
]
)
os.remove(os.path.join(path, file))
return return_code
def download_package_abi3(path, platform, version, implementation, abi, name, pk_version, index_url, filename):
def download_package_abi3(
path, platform, version, implementation, abi, name, pk_version, index_url, filename
):
"""
Download the package when the abi tag is abi3. Install the package to get the dependecies
information from METADATA in dist-info and download all the dependecies.
"""
return_code = subprocess.call([
'python', 'kolibripip.pex', 'download', '-q', '-d', path, '--platform', platform,
'--python-version', version, '--implementation', implementation,
'--abi', abi, '-i', index_url, '--no-deps', '{}=={}'.format(name, pk_version)
])
return_code = subprocess.call([
'python', 'kolibripip.pex', 'install', '-q', '-t', path, os.path.join(path, filename), '--no-deps'
]) or return_code
return_code = subprocess.call(
[
"python",
"kolibripip.pex",
"download",
"-q",
"-d",
path,
"--platform",
platform,
"--python-version",
version,
"--implementation",
implementation,
"--abi",
abi,
"-i",
index_url,
"--no-deps",
"{}=={}".format(name, pk_version),
]
)
return_code = (
subprocess.call(
[
"python",
"kolibripip.pex",
"install",
"-q",
"-t",
path,
os.path.join(path, filename),
"--no-deps",
]
)
or return_code
)
os.remove(os.path.join(path, filename))
# Open the METADATA file inside dist-info folder to find out dependencies.
with open(os.path.join(path, '{}-{}.dist-info'.format(name, pk_version), 'METADATA'), 'r') as metadata:
with open(
os.path.join(path, "{}-{}.dist-info".format(name, pk_version), "METADATA"), "r"
) as metadata:
for line in metadata:
if line.startswith('Requires-Dist:'):
requires_dist = line.rstrip('\n').split('Requires-Dist: ')
content = requires_dist[-1].split('; ')
version_constraint = content[0].split('(')[-1].split(')')[0]
name = content[0].split('(')[0].strip()
if content[-1].startswith('extra ==') or content[-1].startswith('python_version < \'3\''):
if line.startswith("Requires-Dist:"):
requires_dist = line.rstrip("\n").split("Requires-Dist: ")
content = requires_dist[-1].split("; ")
version_constraint = content[0].split("(")[-1].split(")")[0]
name = content[0].split("(")[0].strip()
if content[-1].startswith("extra ==") or content[-1].startswith(
"python_version < '3'"
):
continue
return_code = subprocess.call([
'python', 'kolibripip.pex', 'download', '-q', '-d', path, '--platform', platform,
'--python-version', version, '--implementation', implementation,
'--abi', implementation + version + 'm', '-i', index_url, '{}{}'.format(name, version_constraint)
]) or return_code
return_code = (
subprocess.call(
[
"python",
"kolibripip.pex",
"download",
"-q",
"-d",
path,
"--platform",
platform,
"--python-version",
version,
"--implementation",
implementation,
"--abi",
implementation + version + "m",
"-i",
index_url,
"{}{}".format(name, version_constraint),
]
)
or return_code
)
return return_code
@ -113,31 +207,49 @@ def install_package_by_wheel(path):
# folder has been generated. Skip the installed package and remove the
# dist-info folder.
if os.path.isdir(os.path.join(path, file)):
if file.endswith('.dist-info'):
if file.endswith(".dist-info"):
shutil.rmtree(os.path.join(path, file))
continue
# If the file is py2, py3 compatible, install it into kolibri/dist/cext
# instead of specific platform paths to reduce the size of installer
if 'py2.py3-none-any' in file:
return_code = subprocess.call([
'python', 'kolibripip.pex', 'install', '-q', '-U', '-t',
DIST_CEXT, os.path.join(path, file), '--no-deps'
])
if "py2.py3-none-any" in file:
return_code = subprocess.call(
[
"python",
"kolibripip.pex",
"install",
"-q",
"-U",
"-t",
DIST_CEXT,
os.path.join(path, file),
"--no-deps",
]
)
else:
return_code = subprocess.call([
'python', 'kolibripip.pex', 'install', '-q', '-t',
path, os.path.join(path, file), '--no-deps'
])
return_code = subprocess.call(
[
"python",
"kolibripip.pex",
"install",
"-q",
"-t",
path,
os.path.join(path, file),
"--no-deps",
]
)
if return_code == 1:
sys.exit('\nInstallation failed for package {}.\n'.format(file))
sys.exit("\nInstallation failed for package {}.\n".format(file))
else:
# Clean up the whl file and dist-info folder
os.remove(os.path.join(path, file))
shutil.rmtree(
os.path.join(
path, '-'.join(file.split('-', 2)[:2]) + '.dist-info'), ignore_errors=True)
os.path.join(path, "-".join(file.split("-", 2)[:2]) + ".dist-info"),
ignore_errors=True,
)
def parse_package_page(files, pk_version, index_url): # noqa C901
@ -145,7 +257,7 @@ def parse_package_page(files, pk_version, index_url): # noqa C901
Parse the PYPI and Piwheels link for the package and install the desired wheel files.
"""
for file in files.find_all('a'):
for file in files.find_all("a"):
# We are not going to install the packages if they are:
# * not a whl file
# * not the version specified in requirements.txt
@ -153,7 +265,7 @@ def parse_package_page(files, pk_version, index_url): # noqa C901
# * not macosx or win_x64 platforms,
# since the process of setup wizard has been fast enough
file_name_chunks = file.string.split('-')
file_name_chunks = file.string.split("-")
# When the length of file_name_chunks is 2, it means the file is tar.gz.
if len(file_name_chunks) == 2:
@ -162,27 +274,37 @@ def parse_package_page(files, pk_version, index_url): # noqa C901
package_version = file_name_chunks[1]
package_name = file_name_chunks[0]
python_version = file_name_chunks[2][2:]
platform = file_name_chunks[4].split('.')[0]
platform = file_name_chunks[4].split(".")[0]
implementation = file_name_chunks[2][:2]
abi = file_name_chunks[3]
if package_version != pk_version:
continue
if python_version == '26':
if python_version == "26":
continue
if 'macosx' in platform:
if "macosx" in platform:
continue
if 'win_amd64' in platform and python_version != '34':
if "win_amd64" in platform and python_version != "34":
continue
print('Installing {}...'.format(file.string))
print("Installing {}...".format(file.string))
version_path = os.path.join(DIST_CEXT, file_name_chunks[2])
package_path = get_path_with_arch(platform, version_path, abi, implementation, python_version)
package_path = get_path_with_arch(
platform, version_path, abi, implementation, python_version
)
download_return = download_package(
package_path, platform, python_version, implementation, abi, package_name,
pk_version, index_url, file.string)
package_path,
platform,
python_version,
implementation,
abi,
package_name,
pk_version,
index_url,
file.string,
)
# Successfully download package
if download_return == 0:
@ -190,7 +312,7 @@ def parse_package_page(files, pk_version, index_url): # noqa C901
# Download failed
else:
# see https://github.com/learningequality/kolibri/issues/4656
print('\nDownload failed for package {}.\n'.format(file.string))
print("\nDownload failed for package {}.\n".format(file.string))
# We still need to have the program exit with error
# if something wrong with PyPi download.
@ -200,10 +322,10 @@ def parse_package_page(files, pk_version, index_url): # noqa C901
# Copy the packages in cp34 to cp35, cp36, cp37 due to abi3 tag.
# https://cryptography.io/en/latest/faq/#why-are-there-no-wheels-for-python-3-5-on-linux-or-macos
if index_url == PYPI_DOWNLOAD:
abi3_src = os.path.join(DIST_CEXT, 'cp34', 'Linux')
python_versions = ['cp35', 'cp36', 'cp37']
abi3_src = os.path.join(DIST_CEXT, "cp34", "Linux")
python_versions = ["cp35", "cp36", "cp37"]
for version in python_versions:
abi3_dst = os.path.join(DIST_CEXT, version, 'Linux')
abi3_dst = os.path.join(DIST_CEXT, version, "Linux")
shutil.copytree(abi3_src, abi3_dst)
@ -215,14 +337,14 @@ def install(name, pk_version):
for link in links:
r = requests.get(link + name)
if r.status_code == 200:
files = BeautifulSoup(r.content, 'html.parser')
files = BeautifulSoup(r.content, "html.parser")
parse_package_page(files, pk_version, link)
else:
sys.exit('\nUnable to find package {} on {}.\n'.format(name, link))
sys.exit("\nUnable to find package {} on {}.\n".format(name, link))
files = os.listdir(DIST_CEXT)
for file in files:
if file.endswith('.dist-info'):
if file.endswith(".dist-info"):
shutil.rmtree(os.path.join(DIST_CEXT, file))
@ -233,17 +355,23 @@ def parse_requirements(args):
"""
with open(args.file) as f:
for line in f:
char_list = line.split('==')
char_list = line.split("==")
if len(char_list) == 2:
# Install package according to its name and version
install(char_list[0].strip(), char_list[1].strip())
else:
sys.exit('\nName format in cext.txt is incorrect. Should be \'packageName==packageVersion\'.\n')
sys.exit(
"\nName format in cext.txt is incorrect. Should be 'packageName==packageVersion'.\n"
)
if __name__ == '__main__':
if __name__ == "__main__":
# Parsing the requirement.txt file argument
parser = argparse.ArgumentParser(description="Downloading and installing Python C extensions tool.")
parser.add_argument('--file', required=True, help='The name of the requirements.txt')
parser = argparse.ArgumentParser(
description="Downloading and installing Python C extensions tool."
)
parser.add_argument(
"--file", required=True, help="The name of the requirements.txt"
)
args = parser.parse_args()
parse_requirements(args)

11
build_tools/py2only.py

@ -2,10 +2,11 @@ import os
import shutil
import sys
dest = 'py2only'
futures_dirname = 'concurrent'
dest = "py2only"
futures_dirname = "concurrent"
DIST_DIR = os.path.join(
os.path.dirname(os.path.realpath(os.path.dirname(__file__))), 'kolibri', 'dist')
os.path.dirname(os.path.realpath(os.path.dirname(__file__))), "kolibri", "dist"
)
def hide_py2_modules():
@ -22,7 +23,7 @@ def hide_py2_modules():
from future.standard_library import TOP_LEVEL_MODULES
for module in TOP_LEVEL_MODULES:
if module == 'test':
if module == "test":
continue
# Move the directory of submodules of 'future' inside 'py2only'
@ -35,7 +36,7 @@ def _move_modules_to_py2only(module_name):
shutil.move(module_src_path, module_dst_path)
if __name__ == '__main__':
if __name__ == "__main__":
# Temporarily add `kolibri/dist` to PYTHONPATH to import future
sys.path.append(DIST_DIR)

171
docker/entrypoint.py

@ -38,14 +38,14 @@ DEFAULT_KOLIBRI_PEX_URL = "https://learningequality.org/r/kolibri-pex-latest"
# - KOLIBRI_PROVISIONDEVICE_FACILITY if set, provision facility with this name
# - CHANNELS_TO_IMPORT if set, comma separated list of channel IDs to import
DEFAULT_ENVS = {
'WHICH_PYTHON': 'python2', # or python3 if you prefer; Kolibri don't care
'KOLIBRI_HOME': '/kolibrihome',
'KOLIBRI_HTTP_PORT': '8080',
'KOLIBRI_LANG': 'en',
'KOLIBRI_RUN_MODE': 'demoserver',
'KOLIBRI_PROVISIONDEVICE_PRESET': 'formal', # other options are 'nonformal', 'informal'
'KOLIBRI_PROVISIONDEVICE_SUPERUSERNAME': 'devowner',
'KOLIBRI_PROVISIONDEVICE_SUPERUSERPASSWORD': 'admin123',
"WHICH_PYTHON": "python2", # or python3 if you prefer; Kolibri don't care
"KOLIBRI_HOME": "/kolibrihome",
"KOLIBRI_HTTP_PORT": "8080",
"KOLIBRI_LANG": "en",
"KOLIBRI_RUN_MODE": "demoserver",
"KOLIBRI_PROVISIONDEVICE_PRESET": "formal", # other options are 'nonformal', 'informal'
"KOLIBRI_PROVISIONDEVICE_SUPERUSERNAME": "devowner",
"KOLIBRI_PROVISIONDEVICE_SUPERUSERPASSWORD": "admin123",
}
@ -62,34 +62,37 @@ def set_default_envs():
# Logic to detemine DEPLOY_TYPE and KOLIBRI_PEX_PATH when using pex deploy
############################################################################
# Check for edge case when both URL and BUILDPATH specified
if 'KOLIBRI_PEX_URL' in envs and 'DOCKERMNT_PEX_PATH' in envs:
logging.warning('Using DOCKERMNT_PEX_PATH and ignoring KOLIBRI_PEX_URL.')
del envs['KOLIBRI_PEX_URL']
if "KOLIBRI_PEX_URL" in envs and "DOCKERMNT_PEX_PATH" in envs:
logging.warning("Using DOCKERMNT_PEX_PATH and ignoring KOLIBRI_PEX_URL.")
del envs["KOLIBRI_PEX_URL"]
# CASE A: Running the pex at KOLIBRI_PEX_URL
if 'KOLIBRI_PEX_URL' in envs and 'DOCKERMNT_PEX_PATH' not in envs:
if envs['KOLIBRI_PEX_URL'] == 'default':
envs['KOLIBRI_PEX_URL'] = DEFAULT_KOLIBRI_PEX_URL
pex_name = 'kolibri-latest.pex'
if "KOLIBRI_PEX_URL" in envs and "DOCKERMNT_PEX_PATH" not in envs:
if envs["KOLIBRI_PEX_URL"] == "default":
envs["KOLIBRI_PEX_URL"] = DEFAULT_KOLIBRI_PEX_URL
pex_name = "kolibri-latest.pex"
else:
pex_name = os.path.basename(envs['KOLIBRI_PEX_URL'].split("?")[0]) # in case ?querystr...
envs['DEPLOY_TYPE'] = 'pex'
envs['KOLIBRI_PEX_PATH'] = os.path.join(envs['KOLIBRI_HOME'], pex_name)
pex_name = os.path.basename(
envs["KOLIBRI_PEX_URL"].split("?")[0]
) # in case ?querystr...
envs["DEPLOY_TYPE"] = "pex"
envs["KOLIBRI_PEX_PATH"] = os.path.join(envs["KOLIBRI_HOME"], pex_name)
# CASE B: Running the pex from the /docker/mnt volume
elif 'DOCKERMNT_PEX_PATH' in envs and 'KOLIBRI_PEX_URL' not in envs:
pex_name = os.path.basename(envs['DOCKERMNT_PEX_PATH'])
envs['DEPLOY_TYPE'] = 'pex'
envs['KOLIBRI_PEX_PATH'] = os.path.join(envs['KOLIBRI_HOME'], pex_name)
elif "DOCKERMNT_PEX_PATH" in envs and "KOLIBRI_PEX_URL" not in envs:
pex_name = os.path.basename(envs["DOCKERMNT_PEX_PATH"])
envs["DEPLOY_TYPE"] = "pex"
envs["KOLIBRI_PEX_PATH"] = os.path.join(envs["KOLIBRI_HOME"], pex_name)
# CASE C: If no PEX url is spefified, we'll run kolibri from source code
else:
envs['DEPLOY_TYPE'] = 'source'
envs["DEPLOY_TYPE"] = "source"
# FACILITY CREATION
################################################################################
def get_kolibri_version(kolibri_cmd):
"""
Calls `kolibri_cmd` (list) to extract version information.
@ -97,15 +100,15 @@ def get_kolibri_version(kolibri_cmd):
or a Kolibri pex invocation like ['python', 'some.pex'].
Returns tuple of ints (major, minor), or (None, None) if verison check fails.
"""
MAJOR_MINOR_PAT = re.compile(r'^(?P<major>\d+)\.(?P<minor>\d+)(\.\d+)?.*')
cmd = kolibri_cmd[:] + ['--version']
logging.info('Calling cmd {} to get the Kolibri version information.'.format(cmd))
cmd_str = ' '.join(cmd)
MAJOR_MINOR_PAT = re.compile(r"^(?P<major>\d+)\.(?P<minor>\d+)(\.\d+)?.*")
cmd = kolibri_cmd[:] + ["--version"]
logging.info("Calling cmd {} to get the Kolibri version information.".format(cmd))
cmd_str = " ".join(cmd)
proc = subprocess.Popen(cmd_str, stdout=subprocess.PIPE, shell=True)
line = proc.stdout.readline().decode('utf-8')
line = proc.stdout.readline().decode("utf-8")
m = MAJOR_MINOR_PAT.search(line)
if m:
major, minor = m.groupdict()['major'], m.groupdict()['minor']
major, minor = m.groupdict()["major"], m.groupdict()["minor"]
return int(major), int(minor)
else:
return None, None
@ -118,10 +121,10 @@ def create_facility(kolibri_cmd):
- Kolibri versions in range [0, 0.9) --> SKIP
- Kolibri versions in range [0.9, + --> provisiondevice
"""
logging.info('Running create_facility')
logging.info("Running create_facility")
major, minor = get_kolibri_version(kolibri_cmd)
if major is None or minor is None:
logging.warning('Failed to retrieve Kolibri version. Skipping.')
logging.warning("Failed to retrieve Kolibri version. Skipping.")
return
if major >= 1:
provisiondevice(kolibri_cmd)
@ -129,55 +132,68 @@ def create_facility(kolibri_cmd):
if minor >= 9:
provisiondevice(kolibri_cmd)
else:
logging.info('Skipping automated facility creation step.')
logging.info("Skipping automated facility creation step.")
def provisiondevice(kolibri_cmd):
envs = os.environ
logging.