# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021 CERN.
# Copyright (C) 2021 TU Wien.
# Copyright (C) 2023 ULB Münster.
#
# Invenio-Cli is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Invenio module to ease the creation and management of applications."""
import json
import re
import sys
from os import listdir
from ..helpers.docker_helper import DockerHelper
from ..helpers.process import ProcessResponse, run_cmd, run_interactive
from ..helpers.rdm import rdm_version
from .steps import FunctionStep
[docs]class RequirementsCommands(object):
"""Pre-requirements check."""
@classmethod
def _version_from_string(cls, string):
"""Gets the version from a given string."""
groups = re.search(r"[0-9]*\.[0-9]*\.[0-9]*", string)
return groups.group(0)
@classmethod
def _check_version(cls, binary, version, major, minor=-1, patch=-1, exact=False):
"""Checks a version."""
parts = version.split(".")
if len(parts) != 3:
return ProcessResponse(
error=f"{binary} incorrect version format or not found. "
"Check that it is installed correctly",
status_code=1,
)
parts = [int(num) for num in parts]
version_ok = False
if exact:
major_match = parts[0] == major
minor_match = minor == -1 or parts[1] == minor
patch_match = patch == -1 or parts[2] == patch
version_ok = major_match and minor_match and patch_match
else:
major_higher = parts[0] > major
major_ok = parts[0] >= major
minor_higher = major_ok and parts[1] > minor
minor_ok = major_ok and parts[1] >= minor
patch_ok = minor_ok and parts[2] >= patch
version_ok = major_higher or minor_higher or patch_ok
if version_ok:
return ProcessResponse(
output=f"{binary} version OK. Got {version}.", status_code=0
)
expected_version = major
if minor > -1:
expected_version = f"{major}.{minor}"
if patch > -1:
expected_version = f"{major}.{minor}.{patch}"
return ProcessResponse(
error=f"{binary} wrong version." f"Got {parts} expected {expected_version}",
status_code=1,
)
[docs] @classmethod
def check_node_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the node version."""
# Output comes in the form of 'v14.4.0\n'
try:
result = run_cmd(["node", "--version"])
version = cls._version_from_string(result.output.strip())
return cls._check_version("Node", version, major, minor, patch, exact)
except Exception as err:
return ProcessResponse(error=f"Node not found. Got {err}.", status_code=1)
[docs] @classmethod
def check_npm_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the npm version."""
# Output comes in the form of '6.14.13\n'
try:
result = run_cmd(["npm", "--version"])
version = cls._version_from_string(result.output.strip())
return cls._check_version("NPM", version, major, minor, patch, exact)
except Exception as err:
return ProcessResponse(error=f"NPM not found. Got {err}.", status_code=1)
[docs] @classmethod
def check_python_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the python version."""
# check the version of the currently executed Python, as
# 'invenio-cli' will create a virtualenv with the stated Python
# version (via pipenv) anyway
version_info = sys.version_info
version = "{}.{}.{}".format(
version_info.major,
version_info.minor,
version_info.micro,
)
return cls._check_version("Python", version, major, minor, patch, exact)
[docs] @classmethod
def check_docker_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the docker version."""
# Use JSON Formatted output and parse it
try:
result = run_cmd(["docker", "version", "--format", "json"])
result_json = json.loads(result.output.strip())
version = cls._version_from_string(result_json["Client"]["Version"])
return cls._check_version("Docker", version, major, minor, patch, exact)
except Exception as err:
return ProcessResponse(error=f"Docker not found. Got {err}.", status_code=1)
[docs] @classmethod
def check_docker_compose_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the docker compose version."""
# Output comes in the form of
# 'Docker Compose version v2.17.3\n'
docker_helper = DockerHelper("", local=False)
try:
result = run_cmd(docker_helper.docker_compose + ["version"])
version = cls._version_from_string(result.output.strip())
return cls._check_version(
"Docker Compose", version, major, minor, patch, exact
)
except Exception as err:
return ProcessResponse(
error=f"Docker Compose not found. Got {err}.", status_code=1
)
[docs] @classmethod
def check_imagemagick_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the ImageMagick version."""
# Output comes in the form of 'ImageMagick, version 7.0.11-13\n'
try:
result = run_cmd(["convert", "--version"])
version = cls._version_from_string(result.output.strip())
return cls._check_version(
"ImageMagick", version, major, minor, patch, exact
)
except Exception as err:
return ProcessResponse(
error=f"ImageMagick not found. Got {err}.",
status_code=1,
)
[docs] @classmethod
def check_git_version(cls, major, minor=-1, patch=-1, exact=False):
"""Check the git version."""
# Output comes in the form of 'git version 2.36.1\n'
try:
result = run_cmd(["git", "--version"])
version = cls._version_from_string(result.output.strip())
return cls._check_version("git", version, major, minor, patch, exact)
except Exception as err:
return ProcessResponse(
error=f"git not found. Got {err}.",
status_code=1,
)
[docs] @classmethod
def check_pipenv_installed(cls):
"""Check the pipenv version."""
# Output comes in the form of 'pipenv, version 2020.11.15\n'
try:
result = run_cmd(["pipenv", "--version"])
parts = result.output.strip().split(",")
if parts[0] != "pipenv":
# this might happen if 'pipenv' points to a different binary
return ProcessResponse(
error=f"Pipenv not found. Got {parts[0]}.", status_code=1
)
version = cls._version_from_string(parts[1])
return ProcessResponse(
output=f"Pipenv OK. Got version {version}.", status_code=0
)
except Exception as err:
return ProcessResponse(
error=f"Pipenv not found. Got {result.error}.", status_code=1
)
[docs] @classmethod
def check_dev(cls):
"""Steps to check the development pre-requisites."""
if rdm_version()[0] >= 11:
node_version = 16
npm_version = 7
else:
# for backwards compatibility with v9 (LTS)
node_version = 14
npm_version = 6
steps = [
FunctionStep(
func=cls.check_node_version,
args={"major": node_version, "exact": True},
message="Checking Node version...",
),
FunctionStep(
func=cls.check_npm_version,
args={"major": npm_version, "exact": True},
message="Checking NPM version...",
),
FunctionStep(
func=cls.check_imagemagick_version,
args={"major": 0, "minor": 0},
message="Checking ImageMagick version...",
),
FunctionStep(
func=cls.check_git_version,
args={"major": 0, "minor": 0},
message="Checking git version...",
),
]
return steps
[docs] @classmethod
def check(cls, development=False):
"""Steps to check the pre-requisites."""
steps = [
FunctionStep(
func=cls.check_python_version,
args={"major": 3, "minor": 6},
message="Checking Python version...",
),
FunctionStep(
func=cls.check_pipenv_installed,
message="Checking Pipenv is installed...",
),
FunctionStep(
func=cls.check_docker_version,
args={"major": 0, "minor": 0},
message="Checking Docker version...",
),
FunctionStep(
func=cls.check_docker_compose_version,
args={"major": 1, "minor": 17},
message="Checking Docker Compose version...",
),
]
if development:
steps.extend(cls.check_dev())
return steps