Beispiel #1
0
def generate_api_docs(
    github_url: str,
    main_package: str,
    exit_on_error: bool = True,
) -> None:
    """Generates API documentation via lazydocs.

    Args:
        github_url (str): Github URL
        main_package (str): The main package name to use for docs generation.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """

    command_prefix = ""
    if is_pipenv_environment():
        command_prefix = "pipenv run"
    else:
        # Check lazydocs command
        build_utils.command_exists("lazydocs", exit_on_error=exit_on_error)

    build_utils.run(
        f"{command_prefix} lazydocs --overview-file=README.md"
        f" --src-base-url={github_url}/blob/main {main_package}",
        exit_on_error=exit_on_error,
    )
Beispiel #2
0
def build_distribution(exit_on_error: bool = True) -> None:
    """Build python package distribution.

    Args:
        exit_on_error (bool, optional): If `True`, exit process as soon as error occures. Defaults to True.
    """

    try:
        # Ensure there are no old builds
        rmtree("./dist")
    except OSError:
        pass

    try:
        # Ensure there are no old builds
        rmtree("./build")
    except OSError:
        pass

    # Build the distribution archives
    build_utils.run("python setup.py sdist bdist_wheel clean --all",
                    exit_on_error=exit_on_error)

    # Check twine command
    build_utils.command_exists("twine", exit_on_error=exit_on_error)

    # Check the archives with twine
    build_utils.run("twine check dist/*", exit_on_error=exit_on_error)
Beispiel #3
0
def publish_pypi_distribution(
    pypi_token: str,
    pypi_user: str = "__token__",
    pypi_repository: Optional[str] = None,
    exit_on_error: bool = True,
) -> None:
    """Publish distribution to pypi.

    Args:
        pypi_token (str): Token of PyPi repository.
        pypi_user (str, optional): User of PyPi repository. Defaults to "__token__".
        pypi_repository (Optional[str], optional): PyPi repository. If `None` provided, use the production instance.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    if not pypi_token:
        build_utils.log(
            "PyPI token is required for release (--pypi-token=<TOKEN>)")
        if exit_on_error:
            build_utils.exit_process(1)
        return

    pypi_repository_args = ""
    if pypi_repository:
        pypi_repository_args = f'--repository-url "{pypi_repository}"'

    # Check twine command
    build_utils.command_exists("twine", exit_on_error=exit_on_error)

    # Publish on pypi
    build_utils.run(
        f'twine upload --non-interactive -u "{pypi_user}" -p "{pypi_token}" {pypi_repository_args} dist/*',
        exit_on_error=exit_on_error,
    )
Beispiel #4
0
def build_docker_image(
    name: str,
    version: str,
    build_args: str = "",
    docker_image_prefix: str = "",
    dockerfile: Optional[str] = None,
    additional_build_args: str = "",
    exit_on_error: bool = True,
) -> subprocess.CompletedProcess:
    """Build a docker image from a Dockerfile in the working directory.

    Args:
        name (str): Name of the docker image.
        version (str): Version to use as tag.
        build_args (str, optional): Add additional build arguments for docker build.
        docker_image_prefix (str, optional): The prefix added to the name to indicate an organization on DockerHub or a completely different repository.
        dockerfile (str, optional): Specify a specific Dockerfile. If not specified, the default `Dockerfile` wil be used.
        exit_on_error (bool, optional): If `True`, exit process as soon as an error occurs.

    Returns:
        subprocess.CompletedProcess: Returns the CompletedProcess object of the
    """
    # Check if docker exists on the system
    build_utils.command_exists("docker", exit_on_error=exit_on_error)

    versioned_tag = get_image_name(name=name, tag=version)
    latest_tag = get_image_name(name=name, tag="latest")

    dockerfile_command = ""
    if dockerfile:
        dockerfile_command = " -f " + dockerfile

    completed_process = build_utils.run(
        "docker build "
        + dockerfile_command
        + "-t "
        + versioned_tag
        + " -t "
        + latest_tag
        + " "
        + build_args
        + " ./",
        exit_on_error=exit_on_error,
    )

    if completed_process.returncode > 0:
        build_utils.log(f"Failed to build Docker image {versioned_tag}")
        return completed_process

    if docker_image_prefix:
        remote_versioned_tag = get_image_name(
            name=name, tag=version, image_prefix=docker_image_prefix
        )
        build_utils.run(
            "docker tag " + versioned_tag + " " + remote_versioned_tag,
            exit_on_error=exit_on_error,
        )

    return completed_process
Beispiel #5
0
def release_docker_image(
    name: str, version: str, docker_image_prefix: str, exit_on_error: bool = True
) -> subprocess.CompletedProcess:
    """Push a Docker image to a repository.

    Args:
        name (str): The name of the image. Must not be prefixed!
        version (str): The tag used for the image.
        docker_image_prefix (str): The prefix added to the name to indicate an organization on DockerHub or a completely different repository.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.

    Returns:
        subprocess.CompletedProcess: Returns the CompletedProcess object of the `docker push ...` command.
    """
    # Check if docker exists on the system
    build_utils.command_exists("docker", exit_on_error=exit_on_error)

    if not docker_image_prefix:
        build_utils.log(
            "The flag --docker-image-prefix cannot be blank when pushing a Docker image."
        )
        build_utils.exit_process(build_utils.EXIT_CODE_GENERAL)

    versioned_tag = get_image_name(name=name, tag=version)
    remote_versioned_tag = get_image_name(
        name=name, tag=version, image_prefix=docker_image_prefix
    )
    build_utils.run(
        "docker tag " + versioned_tag + " " + remote_versioned_tag,
        exit_on_error=exit_on_error,
    )
    completed_process = build_utils.run(
        "docker push " + remote_versioned_tag, exit_on_error=exit_on_error
    )

    if completed_process.returncode > 0:
        build_utils.log(f"Failed to release Docker image {name}:{version}")

    # Only push version with latest tag if no suffix is added (pre-release)
    if "-" not in version:
        remote_latest_tag = get_image_name(
            name=name, tag="latest", image_prefix=docker_image_prefix
        )

        build_utils.log(
            "Release Docker image with latest tag as well: " + remote_latest_tag
        )

        build_utils.run(
            "docker tag " + versioned_tag + " " + remote_latest_tag,
            exit_on_error=exit_on_error,
        )
        build_utils.run("docker push " + remote_latest_tag, exit_on_error=exit_on_error)

    return completed_process
Beispiel #6
0
def build_mkdocs(exit_on_error: bool = True) -> None:
    """Build mkdocs markdown documentation.

    Args:
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """

    command_prefix = ""
    if is_pipenv_environment():
        command_prefix = _PIPENV_RUN
    else:
        # Check mkdocs command
        build_utils.command_exists("mkdocs", exit_on_error=exit_on_error)

    build_utils.run(f"{command_prefix} mkdocs build",
                    exit_on_error=exit_on_error)
Beispiel #7
0
def run_dev_mode(port: int = 8001, exit_on_error: bool = True) -> None:
    """Run mkdocs development server.

    Args:
        port (int, optional): Port to use for mkdocs development server. Defaults to 8001.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    build_utils.log(f"Run docs in development mode (http://localhost:{port}):")

    command_prefix = ""
    if is_pipenv_environment():
        command_prefix = _PIPENV_RUN
    else:
        # Check mkdocs command
        build_utils.command_exists("mkdocs", exit_on_error=exit_on_error)

    build_utils.run(
        f"{command_prefix} mkdocs serve --dev-addr 0.0.0.0:{port}",
        exit_on_error=exit_on_error,
    )
Beispiel #8
0
def deploy_gh_pages(exit_on_error: bool = True) -> None:
    """Deploy mkdocs documentation to Github pages.

    Args:
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    build_utils.log("Deploy documentation to Github pages:")

    command_prefix = ""
    if is_pipenv_environment():
        command_prefix = _PIPENV_RUN
    else:
        # Check mkdocs command
        build_utils.command_exists("mkdocs", exit_on_error=exit_on_error)

    build_utils.run(
        f"{command_prefix} mkdocs gh-deploy --clean",
        exit_on_error=exit_on_error,
        timeout=120,
    )
Beispiel #9
0
def install_build_env(exit_on_error: bool = True) -> None:
    """Installs a new virtual environment via pipenv.

    Args:
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    # Check if pipenv exists
    build_utils.command_exists("pipenv", exit_on_error=exit_on_error)

    if not os.path.exists("Pipfile"):
        build_utils.log(
            "No Pipfile discovered, cannot install pipenv environemnt")
        if exit_on_error:
            build_utils.exit_process(1)
        return

    build_utils.run("pipenv --rm", exit_on_error=False)
    build_utils.run(
        f"pipenv install --dev --python={sys.executable} --skip-lock --site-packages",
        exit_on_error=exit_on_error,
    )
Beispiel #10
0
def test_with_py_version(python_version: str,
                         exit_on_error: bool = True) -> None:
    """Run pytest in a environment wiht the specified python version.

    Args:
        python_version (str): Python version to use inside the virutal environment.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    if not os.path.exists("Pipfile"):
        build_utils.log(
            "No Pipfile discovered. Testing with specific python version only works with pipenv."
        )
        return

    # Check if pyenv command exists
    build_utils.command_exists("pyenv", exit_on_error=exit_on_error)

    # Check if pipenv command exists
    build_utils.command_exists("pipenv", exit_on_error=exit_on_error)

    # Install pipenv environment with specific versio
    build_utils.run(
        f"pyenv install --skip-existing {python_version} && pyenv local {python_version}",
        exit_on_error=exit_on_error,
    )
    # Install pipenv environment with specific version
    build_utils.run(
        f"pipenv install --dev --python={python_version} --skip-lock",
        exit_on_error=exit_on_error,
    )
    # Run pytest in pipenv environment
    build_utils.run("pipenv run pytest", exit_on_error=exit_on_error)
    # Remove enviornment
    build_utils.run("pipenv --rm", exit_on_error=False)
    # Uninstall pyenv version
    build_utils.run(
        f"pyenv local --unset && pyenv uninstall -f {python_version}",
        exit_on_error=False,
    )
Beispiel #11
0
def is_pipenv_environment() -> bool:
    """Check if current working directory is a valid pipenv environment."""

    if not os.path.exists("Pipfile"):
        return False

    if not build_utils.command_exists("pipenv"):
        return False

    return (build_utils.run(
        "pipenv --venv",
        disable_stderr_logging=True,
        disable_stdout_logging=True,
        exit_on_error=False,
    ).returncode == 0)
Beispiel #12
0
def lint_markdown(markdownlint: bool = True,
                  exit_on_error: bool = True) -> None:
    """Run markdownlint on markdown documentation.

    Args:
        markdownlint (bool, optional): Activate markdown linting via `markdownlint`. Defaults to `True`.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    build_utils.log("Run linters and style checks:")

    if markdownlint and build_utils.command_exists(
            "markdownlint", exit_on_error=exit_on_error):
        config_file_arg = ""
        if os.path.exists(".markdown-lint.yml"):
            config_file_arg = "--config='.markdown-lint.yml'"

        build_utils.run(f"markdownlint {config_file_arg} ./docs",
                        exit_on_error=exit_on_error)
Beispiel #13
0
def check_image(
    image: str, trivy: bool = True, exit_on_error: bool = True
) -> subprocess.CompletedProcess:
    """Run vulnerability checks on Dockerimage.

    Args:
        image (str): The name of the docker image to check.
        trivy (bool, optional): Activate trivy vulnerability check. Defaults to `True`.
        exit_on_error (bool, optional): If `True`, exit process as soon as an error occurs.
    """
    build_utils.log("Run vulnerability checks on docker image:")

    if trivy and build_utils.command_exists("trivy", exit_on_error=exit_on_error):
        return build_utils.run(
            f"trivy image --timeout=20m0s --exit-code 1 --severity HIGH,CRITICAL {image}",
            exit_on_error=exit_on_error,
        )

    return subprocess.CompletedProcess(args="", returncode=-1, stdout="", stderr="")
Beispiel #14
0
def lint_dockerfile(
    hadolint: bool = True, dockerfile: str = "Dockerfile", exit_on_error: bool = True
) -> None:
    """Run hadolint on the Dockerfile.

    Args:
        hadolint (bool, optional): Activate hadolint dockerfile linter. Defaults to `True`.
        dockerfile (str, optional): Specify a specific Dockerfile. If not specified, the default `Dockerfile` wil be used.
        exit_on_error (bool, optional): Exit process if an error occurs. Defaults to `True`.
    """
    build_utils.log("Run linters and style checks:")

    if hadolint and build_utils.command_exists("hadolint", exit_on_error=exit_on_error):
        config_file_arg = ""
        if os.path.exists(".hadolint.yml"):
            config_file_arg = "--config=.hadolint.yml"

        build_utils.run(
            f"hadolint {config_file_arg} {dockerfile}", exit_on_error=exit_on_error
        )
Beispiel #15
0
def code_checks(
    black: bool = True,
    isort: bool = True,
    pydocstyle: bool = True,
    mypy: bool = True,
    flake8: bool = True,
    safety: bool = False,
    exit_on_error: bool = True,
) -> None:
    """Run linting and style checks.

    Args:
        black (bool, optional): Activate black formatting check. Defaults to True.
        isort (bool, optional): Activate isort import sorting check. Defaults to True.
        pydocstyle (bool, optional): Activate pydocstyle docstring check. Defaults to True.
        mypy (bool, optional): Activate mypy typing check. Defaults to True.
        flake8 (bool, optional): Activate flake8 linting check. Defaults to True.
        safety (bool, optional): Activate saftey check via pipenv. Defaults to False.
        exit_on_error (bool, optional): If `True`, exit process as soon as error occures. Defaults to True.
    """

    command_prefix = ""
    if is_pipenv_environment():
        command_prefix = "pipenv run"

    successful: bool = True

    if black:
        if not command_prefix:
            # Check twine command
            build_utils.command_exists("black", exit_on_error=exit_on_error)

        if (build_utils.run(f"{command_prefix} black --check src",
                            exit_on_error=False).returncode > 0):
            successful = False

        if (build_utils.run(f"{command_prefix} black --check tests",
                            exit_on_error=False).returncode > 0):
            successful = False

    if isort:
        if not command_prefix:
            # Check twine command
            build_utils.command_exists("isort", exit_on_error=exit_on_error)

        if (build_utils.run(
                f"{command_prefix} isort --profile black --check-only src",
                exit_on_error=False,
        ).returncode > 0):
            successful = False

        if (build_utils.run(
                f"{command_prefix} isort --profile black --check-only tests",
                exit_on_error=False,
        ).returncode > 0):
            successful = False

    if pydocstyle:
        if not command_prefix:
            # Check twine command
            build_utils.command_exists("pydocstyle",
                                       exit_on_error=exit_on_error)

        if (build_utils.run(f"{command_prefix} pydocstyle src",
                            exit_on_error=False).returncode > 0):
            successful = False

        # Run linters and checks
    if mypy:
        if not command_prefix:
            # Check twine command
            build_utils.command_exists("mypy", exit_on_error=exit_on_error)

        if (build_utils.run(f"{command_prefix} mypy src",
                            exit_on_error=False).returncode > 0):
            successful = False

    if flake8:
        if not command_prefix:
            # Check twine command
            build_utils.command_exists("flake8", exit_on_error=exit_on_error)

        if (build_utils.run(
                f"{command_prefix} flake8 --show-source --statistics src",
                exit_on_error=False,
        ).returncode > 0):
            successful = False

        if (build_utils.run(
                f"{command_prefix} flake8 --show-source --statistics tests",
                exit_on_error=False,
        ).returncode > 0):
            successful = False

    if safety:
        # Check pipenv command
        build_utils.command_exists("pipenv", exit_on_error=exit_on_error)

        # Check using pipenv (runs safety check)
        if build_utils.run("pipenv check", exit_on_error=False).returncode > 0:
            successful = False

    if not successful:
        build_utils.log(
            "Code checks (style, linting, safety, ...) failed. Please check the logs and fix the issues."
        )
        build_utils.exit_process(1)