Kerala Cyber
Warriors
KCW Uploader V1.1
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms,
# as designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import json
import os
import re
import shutil
import zipfile
import mysqlsh
from enum import Enum
from . import general
from . import repositories
from mysqlsh.plugin_manager import validate_shell_version
class Filter(Enum):
NONE = "all plugins"
ONLY_INSTALLED = "installed plugins"
NOT_INSTALLED = "available plugins for installation"
UPDATABLE = "updatable plugins"
def parse_version(version):
ret_val = tuple(int(x) if x.isnumeric() else x for x in version.split("."))
return ret_val
def get_repositories_with_plugins(plugin_filter=Filter.NONE,
printouts=True,
raise_exceptions=False):
"""Downloads all plugin information from the available repositories
Args:
printouts (bool): Whether to print out information
raise_exceptions (bool): Whether exceptions should be raised
Returns:
A list of repositories including their plugins
"""
if printouts:
print(f"Fetching list of {plugin_filter.value}...\n")
# Get the list of all repositories, cSpell:ignore repos
repos = repositories.get_plugin_repositories(return_formatted=False)
try:
i = 1
for r in repos:
# If the manifest is stored in a zip, extact that zip and get the
# manifest file
if r.get("url").endswith(".zip"):
# Download the zip file
zip_file_path = general.get_shell_temp_dir("manifest.zip")
# If there is an earlier manifest.zip, remove it
if os.path.isfile(zip_file_path):
os.remove(zip_file_path)
if not general.download_file(r.get("url"), zip_file_path):
continue
# Extract the zip file
manifest_dir = general.get_shell_user_dir(
"temp", "manifest_zip")
if os.path.isdir(manifest_dir):
shutil.rmtree(manifest_dir)
try:
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
zip_ref.extractall(manifest_dir)
except zipfile.BadZipFile as e:
print(f"ERROR: Could not extract zip file {zip_file_path}."
f"\n{str(e)}")
if raise_exceptions:
raise
# Remove zip file
os.remove(zip_file_path)
zip_manifest_path = r.get("manifestPath")
if not zip_manifest_path:
zip_manifest_path = "mysql-shell-plugins-manifest.json"
manifest_path = manifest_dir
for path_item in zip_manifest_path.split('/'):
manifest_path = os.path.join(manifest_path, path_item)
if not os.path.isfile(manifest_path):
shutil.rmtree(manifest_dir)
print(f"ERROR: Cannot open file '{manifest_path}'.")
return
with open(manifest_path, "r") as manifest_file:
manifest = manifest_file.read()
shutil.rmtree(manifest_dir)
else:
manifest = general.download_file(r.get("url"))
# Load the current plugin manifest of the repo
if manifest:
plugins = json.loads(manifest).get("plugins")
# Add id, installed, installedVersion,
# installedDevelopmentStage and updateAvailable fields to all
# plugins
for p in plugins:
p["id"] = i
i += 1
# Set installed, installedVersion and updateAvailable
name = p.get("name")
installed = name in dir(mysqlsh.globals)
p["installed"] = installed
if installed:
latest_version = p.get("latestVersion")
plugin = getattr(mysqlsh.globals, name)
if plugin and "version" in dir(plugin):
installed_version = plugin.version()
p["installedVersion"] = installed_version
if parse_version(latest_version) > \
parse_version(installed_version):
p["updateAvailable"] = True
version_data = get_plugin_version_data(
p, installed_version)
if version_data:
installed_dev_stage = version_data.get(
"developmentStage")
if installed_dev_stage:
p["installedDevelopmentStage"] = \
installed_dev_stage.upper()
# Filter out installed plugins if needed
if plugin_filter == Filter.ONLY_INSTALLED:
plugins = [p for p in plugins if p.get("installed")]
elif plugin_filter == Filter.NOT_INSTALLED:
plugins = [p for p in plugins if not p.get("installed")]
elif plugin_filter == Filter.UPDATABLE:
plugins = [
p for p in plugins if p.get("updateAvailable", False)
]
# Add list of plugins to the repo
r["plugins"] = plugins
return repos
except json.JSONDecodeError as e:
print(f"ERROR: Could not parse JSON file. {str(e.msg)}, "
f"line: {e.lineno}, col: {e.colno}")
if raise_exceptions:
raise
except Exception as e:
print(f"ERROR: Could not get the list of repositories.\n{str(e)}")
if raise_exceptions:
raise
def get_plugin_version_data(plugin, version):
"""Returns the plugin version data for a given version
Args:
plugin (dict): A dict representing a plugin
version (str): The version to look up
Returns:
The version data as a dict or None if the version is not found
"""
versions = plugin.get("versions")
if not versions:
return
for v in versions:
if v.get("version") == version:
return v
def get_plugin_count(repositories):
"""Returns the total number of plugin
Args:
repositories (dict): A list of repositories with their plugins.
Returns:
The number of plugins
"""
i = 0
if repositories:
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
i += len(plugins)
return i
def get_installed_plugin_count(repositories):
"""Returns the total number of plugin
Args:
repositories (dict): A list of repositories with their plugins.
Returns:
The number of plugins
"""
i = 0
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
for p in plugins:
if p.get("installed", False):
i += 1
return i
def get_available_update_count(repositories):
"""Returns the number of available plugin updates
Args:
repositories (dict): A list of repositories with their plugins.
print_info (bool): If set to True, information about the updates will
be printed
Returns:
A tuple containing the number of available updates and info text
"""
updateable_count = 0
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
for p in plugins:
if p.get("updateAvailable", False):
updateable_count += 1
if updateable_count == 1:
out = (f"One update is available. "
"Use plugins.update() to install the update.")
elif updateable_count > 1:
out = (f"{updateable_count} updates are available. "
"Use plugins.update() to install the updates.")
else:
out = "No updates available."
return updateable_count, out
def format_plugin_listing(repositories, add_description=True):
"""Returns a formatted list of plugins.
Args:
repositories (list): A list of repositories with their plugins.
Returns:
The formated list as string
"""
i = 1
out = ""
header = (f" # {'Name':20} {'Caption':34} {'Version':16} "
f"{'Installed':16}")
sep = f"---- {'-'*20} {'-'*34} {'-'*16} {'-'*16}"
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
repo_name = r.get('name', 'Repository: ?')
if out == "":
out += f"{header}\n{sep}\n"
else:
if not add_description:
out += '\n'
#out += f"{repo_name}\n{'='*94}\n"
for p in plugins:
id = p.get("id", i)
name = p.get("name")
name = name[:18] + '..' if len(name) > 20 else name
# Caption
caption = p.get("caption")
caption = re.sub(
r'[\n\r]', ' ',
caption[:32] + '..' if len(caption) > 34 else caption)
# Description
desc = p.get("description")
desc = re.sub(r'[\n\r]', ' ',
desc[:73] + '..' if len(desc) > 75 else desc)
# Latest Version
latest_v = p.get("latestVersion")
# Development Stage
dev_stage = ""
version_data = get_plugin_version_data(p, latest_v)
if version_data:
dev_stage = version_data.get("developmentStage")
if dev_stage:
dev_stage = dev_stage.upper()
version_str = latest_v + " " + dev_stage
# Installed Version
installed = p.get("installed", False)
installed_version = p.get("installedVersion", "?.?.?")
installed_dev_stage = p.get("installedDevelopmentStage", "??")
update_available = "^" if p.get("updateAvailable", False) else ""
installed_str = installed_version + " " + installed_dev_stage + \
update_available
if installed:
out += f"*{id:>3} "
else:
out += f"{id:>4} "
out += (f"{name:20} {caption:34} {version_str:16} "
f"{installed_str if installed else 'No':16}\n")
if add_description:
out += f" {desc}\n\n"
i += 1
return out
def get_plugin(repositories, name=None, interactive=True):
"""Returns a plugin dict
Args:
repositories (list): A list of repositories with their plugins.
name (str): The name of the plugin
interactive (bool): Whether user input is accepted
Returns:
A dict representing the plugin
"""
selected_item = None
plugin_count = get_plugin_count(repositories)
# If there are no plugins, return None
if plugin_count == 0:
print("No plugins available.")
return
# If no name is given but there is only one plugin, return that one
# if name is None and plugin_count == 1:
# for r in repositories:
# plugins = r.get("plugins")
# if not plugins:
# continue
# if len(plugins) > 0:
# selected_item = plugins[0]
if name is None and interactive:
out = format_plugin_listing(repositories, add_description=False)
print(out)
# Let the user choose from the list
while selected_item is None:
prompt = mysqlsh.globals.shell.prompt(
"Please enter the index or name of a plugin: ").strip().lower(
)
if not prompt:
print("Operation cancelled.")
return
plugin_count = get_plugin_count(repositories)
try:
try:
# If the user provided an index, try to map that
nr = int(prompt)
if nr > 0 and nr <= plugin_count:
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
for p in plugins:
if nr == p.get("id"):
selected_item = p
break
if selected_item:
break
else:
raise IndexError
except ValueError:
# Search by name
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
for p in plugins:
if prompt == p.get("name"):
selected_item = p
break
if selected_item:
break
if selected_item is None:
raise ValueError
except (ValueError, IndexError):
print(f"The plugin '{prompt}' was not found. Please try again "
"or leave empty to cancel the operation.\n")
return
# Add empty line
print("")
else:
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
for p in plugins:
if name == p.get("name"):
selected_item = p
break
if selected_item:
break
if not selected_item:
print(f"The plugin '{name}' was not found.")
return selected_item
def list_plugins(**kwargs):
"""Lists plugins all available MySQL Shell plugins.
This function will list all all available plugins in the registered
plugin repositories. To add a new plugin repository use the
plugins.repositories.add() function.
Args:
**kwargs: Optional parameters
Keyword Args:
return_formatted (bool): If set to true, a list object is returned.
interactive (bool): Whether user input is accepted
Returns:
None or a list of dicts representing the plugins
"""
return_formatted = kwargs.get('return_formatted', True)
interactive = kwargs.get('interactive', True)
# Get available repositories including the plugin lists
repositories = get_repositories_with_plugins()
if not repositories:
return
if not return_formatted:
return repositories
else:
# Get number of available updates
updates_available, update_info = get_available_update_count(
repositories)
out = format_plugin_listing(repositories)
plugin_count = get_plugin_count(repositories=repositories)
inst_count = get_installed_plugin_count(repositories=repositories)
if inst_count > 0:
out += (f"* {inst_count} plugin{'s' if inst_count > 1 else ''} "
"installed, ")
if plugin_count > 0:
out += (f"{plugin_count} plugin"
f"{'s' if plugin_count > 1 else ''}"
" total.")
# If there are available updates, add that information
if updates_available > 0:
out += f"\n^ {update_info}"
if interactive:
out += (
"\n\nUse plugins.details() to get more information about a "
"specific plugin.\n")
else:
out += "\n"
return out
def install_plugin(name=None, **kwargs):
"""Installs a MySQL Shell plugin.
This function download and install a plugin
Args:
name (str): The name of the plugin.
**kwargs: Optional parameters
Keyword Args:
version (str): If specified, that specific version of the plugin will
be installed
force_install (bool): It set to true will first remove the plugin
if it already exists
return_object (bool): Whether to return the object
interactive (bool): Whether user input is accepted
printouts (bool): Whether information should be printed
raise_exceptions (bool): Whether exceptions are raised
Returns:
None or plugin information
"""
version = kwargs.get("version")
force_install = kwargs.get("force_install")
return_object = kwargs.get("return_object", False)
interactive = kwargs.get("interactive", True)
printouts = kwargs.get("printouts", True)
raise_exceptions = kwargs.get("raise_exceptions", False)
try:
# Get the list of repositories and their plugins
repositories = get_repositories_with_plugins(
plugin_filter=Filter.NONE
if name or version or force_install else Filter.NOT_INSTALLED,
printouts=printouts)
if not repositories:
return
# Get the plugin by name or interactive user selection
plugin = get_plugin(repositories, name, interactive)
if plugin is None:
return
# Get plugin name and caption
name = plugin.get('name')
caption = plugin.get('caption')
module_name = plugin.get('moduleName')
installed = name in dir(mysqlsh.globals)
if installed and not force_install:
print(f"The plugin '{name}' is already installed. Use the "
"force_install parameter to re-install it anyway.\n")
return
# Get version data
version = version if version else plugin.get('latestVersion')
version_data = get_plugin_version_data(plugin=plugin, version=version)
if not version_data:
print(
f"ERROR: Version {version} not found for plugin {caption}.\n")
return
shell_version_min = version_data.get('shellVersionMin',None)
shell_version_max = version_data.get('shellVersionMax',None)
try:
validate_shell_version(min=shell_version_min, max=shell_version_max)
except Exception as e:
print(str(e))
return
if printouts:
print(f"Installing {caption} ...")
# Get the right download URL for the plugin
urls = version_data.get('urls')
if not urls:
print(f"ERROR: No URLs available for plugin {caption}.\n")
return
# TODO: Check if the shell binary is a community or commercial build
url = urls.get('community')
zip_file_name = url.split('/')[-1]
zip_file_path = general.get_shell_temp_dir(zip_file_name)
# If the zip file already exists, delete it
if os.path.isfile(zip_file_path):
os.remove(zip_file_path)
# Download the file
if not general.download_file(url=url, file_path=zip_file_path):
return
# Set plugin directory using the module_name of the plugin
plugin_dir = general.get_plugins_dir(module_name)
# If the plugin folder already exists, delete it
if force_install and os.path.isdir(plugin_dir):
shutil.rmtree(plugin_dir)
# Extract the zip file
try:
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
zip_ref.extractall(plugin_dir)
except zipfile.BadZipFile as e:
print(
f"ERROR: Could not extract zip file {zip_file_path}.\n{str(e)}"
)
if raise_exceptions:
raise
# Remove zip file
os.remove(zip_file_path)
if printouts:
print(f"{caption} has been installed successfully.\n\n"
f"Please restart the shell to load the plugin. To get help "
f"type '\\? {name}' after restart.\n")
if return_object:
return plugin
except Exception as e:
print(f"ERROR: Could not install the plugin.\n{str(e)}")
if raise_exceptions:
raise
def uninstall_plugin(name=None, **kwargs):
"""Uninstalls a MySQL Shell plugin.
This function uninstall a plugin
Args:
name (str): The name of the plugin.
**kwargs: Optional parameters
Keyword Args:
interactive (bool): Whether user input is accepted
printouts (bool): Whether information should be printed
raise_exceptions (bool): Whether exceptions are raised
Returns:
None or plugin information
"""
interactive = kwargs.get("interactive", True)
printouts = kwargs.get("printouts", True)
raise_exceptions = kwargs.get("raise_exceptions", False)
try:
# Get the list of repositories and their plugins
repositories = get_repositories_with_plugins(
plugin_filter=Filter.ONLY_INSTALLED, printouts=printouts)
if not repositories:
return
# Get the plugin by name or interactive user selection
plugin = get_plugin(repositories, name, interactive)
if plugin is None:
print("Cancelling operation.")
return
# Get plugin name and caption
name = plugin.get('name')
caption = plugin.get('caption')
module_name = plugin.get('moduleName')
if interactive:
prompt = mysqlsh.globals.shell.prompt(
f"\nAre you sure you want to uninstall the plugin '{name}'"
" [YES/no]: ", {
'defaultValue': 'yes'
}).strip().lower()
if prompt != 'yes':
print("Operation cancelled.")
return
if printouts:
print(f"Uninstalling {caption} ...")
# Get plugins_dir
plugins_dir = general.get_plugins_dir()
# Set plugin directory using the name of the plugin
plugin_dir = os.path.join(plugins_dir, module_name)
# Delete plugin directory and its contents
shutil.rmtree(plugin_dir)
if printouts:
print(f"{caption} has been uninstalled successfully.\n\n"
f"Please restart the shell to unload the plugin.\n")
except Exception as e:
print(f"ERROR: Could not uninstall the plugin.\n{str(e)}")
if raise_exceptions:
raise
def update_plugin(name=None, **kwargs):
"""Updates MySQL Shell plugins.
This function updates on or all plugins
Args:
name (str): The name of the plugin.
**kwargs: Optional parameters
Keyword Args:
interactive (bool): Whether user input is accepted
raise_exceptions (bool): Whether exceptions are raised
Returns:
None or plugin information
"""
interactive = kwargs.get("interactive", True)
raise_exceptions = kwargs.get("raise_exceptions", False)
try:
# Get available repositories including the plugin lists
repositories = get_repositories_with_plugins(
plugin_filter=Filter.UPDATABLE)
if not repositories:
return
# Build list of plugins and ask the user to confirm when interactive is
# set
plugins = []
plugin_count = get_plugin_count(repositories=repositories)
if plugin_count == 0:
print("No updates available.\n")
return
elif plugin_count == 1:
plugin = None
if name:
plugin = get_plugin(repositories=repositories,
name=name,
interactive=False)
if not plugin:
print(f"No update available for plugin '{name}'.")
return
elif plugin_count == 1:
plugin = get_plugin(repositories=repositories)
if interactive:
caption = plugin.get("caption")
prompt = mysqlsh.globals.shell.prompt(
f"Are you sure you want to update the '{caption}' "
"[YES/no]: ", {
'defaultValue': 'yes'
}).strip().lower()
if prompt != 'yes':
print("Operation cancelled.")
return
plugins.append(plugin)
else:
for r in repositories:
plugins = r.get("plugins")
if not plugins:
continue
plugins.append(plugins)
print("There are updates pending for the following plugins:")
for plugin in plugins:
print(f" - {plugin.get('caption')}")
prompt = mysqlsh.globals.shell.prompt(
f"\nAre you sure you want to update these plugins [YES/no]: ",
{
'defaultValue': 'yes'
}).strip().lower()
if prompt != 'yes':
print("Operation cancelled.")
return
# Perform the updates
for plugin in plugins:
name = plugin.get("name")
caption = plugin.get("caption")
try:
print(f"Updating {caption} ...")
install_plugin(name=name,
force_install=True,
interactive=False,
printouts=False,
raise_exceptions=True)
print(f"\nThe update{'s have' if len(plugins) > 1 else ' has'}"
" been completed successfully.\n\n"
"Please restart the shell to reload the plugin.\n")
except Exception as e:
print(f"ERROR: Could not update plugin '{name}'.\n{str(e)}")
if raise_exceptions:
raise
except Exception as e:
print(f"ERROR: Could not successfully update all plugins.\n{str(e)}")
if raise_exceptions:
raise
def plugin_details(name=None, **kwargs):
"""Gives detailed information about a MySQL Shell plugin.
Args:
name (str): The name of the plugin.
**kwargs: Optional parameters
Keyword Args:
interactive (bool): Whether user input is accepted
Returns:
None or plugin information
"""
interactive = kwargs.get("interactive", True)
# Get available repositories including the plugin lists
repositories = get_repositories_with_plugins()
# Get a plugin to print details about
plugin = get_plugin(repositories=repositories,
name=name,
interactive=interactive)
if not plugin:
return
caption = plugin.get('caption', '')
description = plugin.get('description', '').replace("\n", f"\n{' ' * 13}")
latest_v = plugin.get('latestVersion', '')
v_data = get_plugin_version_data(plugin=plugin, version=latest_v)
latest_v_dev_stage = v_data.get('developmentStage', '').upper()
shell_ver_min = v_data.get('shellVersionMin','')
shell_ver_max = v_data.get('shellVersionMax','')
versions = plugin.get("versions", [])
print(f"{caption}\n{'-' * len(caption)}\n"
f"{'Plugin Name:':>20} {plugin.get('name', '')}\n"
f"{'Latest Version:':>20} {latest_v}\n"
f"{'Dev Stage:':>20} {latest_v_dev_stage}\n"
f"{'Min. Shell Version:':>20} {shell_ver_min}\n"
f"{'Max. Shell Version:':>20} {shell_ver_max}\n"
f"{'Description:':>20} {description}\n"
f"{'Available Versions:':>20} {len(versions)}")
i = 0
for version in versions:
v_str = (f"{version.get('version', '?.?.?')} "
f"{version.get('developmentStage', '?').upper()}")
v_str = f"{v_str:>18}"
out = v_str
change_list = version.get("changes")
if change_list and len(change_list) > 0:
changes = ""
for change in change_list:
if changes == "":
changes += " - "
else:
changes += f"{' ' * len(v_str)} - "
changes += change + "\n"
out += changes
print(out)
i += 1
if interactive and i == 5 and len(versions) > 5:
rem = len(versions) - i
prompt = mysqlsh.globals.shell.prompt(
f"Do you want to print the remaining list of "
f"{rem} version{'s' if rem > 1 else ''}? [yes/NO]: ", {
'defaultValue': 'no'
}).strip().lower()
if prompt != 'yes':
break
-=[ KCW uplo4d3r c0ded by cJ_n4p573r ]=-
Ⓒ2017 ҠЄГѦLѦ СүѣЄГ ЩѦГГіѺГՏ