Skip to content
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

feat: create a new command to run extra tutor commands #59

Merged
merged 15 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ tutor distro repository-validator
# Enabler commands
tutor distro enable-themes
tutor distro enable-private-packages

# Run extra commands
tutor distro run-extra-commands
```

### Documentation
Expand Down Expand Up @@ -240,6 +243,21 @@ The command will check the configuration for:
- INSTALL_EXTRA_FILE_REQUIREMENTS
- OPENEDX_EXTRA_SETTINGS

# Run tutor extra commands

You can run tutor extra commands by adding them into the **config.yml** in an attribute `DISTRO_EXTRA_COMMANDS` like this:

```yaml
DISTRO_EXTRA_COMMANDS:
- tutor plugins install mfe && tutor plugins enable mfe
- tutor plugins index add https://overhang.io/tutor/main
```
You can only insert commands enabled by the [Tutor CLI](https://docs.tutor.edly.io/reference/cli/index.html). Once you have added the commands you want to execute, you will need to run the following command:

```bash
tutor distro run-extra-commands
```

# Other Options

## How to add custom middlewares
Expand Down
14 changes: 14 additions & 0 deletions features/run_extra_commands.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Feature: Run extra commands
@fixture.behave.tutor_root
Scenario: Execute the extra commands from config.yml properly
Given There is a tutor root
And There is a config.yml file
And There are valid commands defined
When I write the command tutor distro run-extra-commands and commands will be properly executed

@fixture.behave.tutor_root
Scenario: Execute commands that are not valid
Given There is a tutor root
And There is a config.yml file
And There are invalid commands defined
When I write the command tutor distro run-extra-commands and commands execution will fail
64 changes: 64 additions & 0 deletions features/steps/run_extra_commands_step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os

import subprocess

from behave import given, when, then # pylint: disable=no-name-in-module
from click.testing import CliRunner
from tutor import config as tutor_config

from tutordistro.commands.run_extra_commands import run_extra_commands


@given("There are valid commands defined")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
extra_commands = [
"tutor plugins update",
"tutor plugins install forum",
"tutor plugins enable forum"
]

config = context.scenario.config
config.update({
"DISTRO_EXTRA_COMMANDS": extra_commands
})

tutor_config.save_config_file(context.scenario.tutor_root, config)
config = tutor_config.load(context.scenario.tutor_root)
context.scenario.config = config
context.scenario.extra_commands = "DISTRO_EXTRA_COMMANDS"

assert "DISTRO_EXTRA_COMMANDS" in config


@given("There are invalid commands defined")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
extra_commands = [
"pip install application"
]

config = context.scenario.config
config.update({
"DISTRO_EXTRA_COMMANDS": extra_commands
})

tutor_config.save_config_file(context.scenario.tutor_root, config)
config = tutor_config.load(context.scenario.tutor_root)
context.scenario.config = config
context.scenario.extra_commands = "DISTRO_EXTRA_COMMANDS"

assert "DISTRO_EXTRA_COMMANDS" in config


@when("I write the command tutor distro run-extra-commands and commands will be properly executed")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
runner = CliRunner()
result = runner.invoke(run_extra_commands, obj=context)
assert result.exit_code == 0


@when("I write the command tutor distro run-extra-commands and commands execution will fail")
def step_impl(context): # pylint: disable=function-redefined,missing-function-docstring
runner = CliRunner()
result = runner.invoke(run_extra_commands, obj=context)
assert result.exit_code != 0

Empty file.
Empty file.
109 changes: 109 additions & 0 deletions tests/distro/run_extra_commands/application/test_run_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Test run commands application.
"""

import pytest

from tests.distro.run_extra_commands.infrastructure.test_tutor_commands import TestTutorCommandManager
from tutordistro.distro.extra_commands.application.commands_runner import CommandsRunner
from tutordistro.distro.share.domain.command_error import CommandError
from tutordistro.utils.common import split_string
from tutordistro.utils.constants import COMMAND_CHAINING_OPERATORS


def test_valid_tutor_command():
"""
Test running valid commands.

This test verifies that are executed all the extra commands successfully.
"""
# Given
valid_tutor_commands = [
"command with word tutor 1",
"command with word tutor 2",
"command with word tutor 3",
]

tutor_commands_manager = TestTutorCommandManager()
run_tutor_command = CommandsRunner(
commands_manager=tutor_commands_manager, commands=valid_tutor_commands
)

# When
for command in valid_tutor_commands:
run_tutor_command(command=command)

assert tutor_commands_manager.commands_ran == len(valid_tutor_commands)


def test_invalid_or_misspelled_tutor_command():
"""
Test running invalid commands.

This test verifies that the execution fails when is
intended to execute invalid extra commands.
"""
# Given
invalid_tutor_command = [
"pip command 1",
"tutor command && pip command 2",
"tutor command & pip command 3",
"tutor command || pip command 4",
"tutor command | pip command 5",
"tutor command ; pip command 6",
]

with pytest.raises(CommandError) as command_error:
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(
commands_manager=tutor_commands_manager, commands=invalid_tutor_command
)

assert command_error.type is CommandError

splitted_commands = [
split_string(command, COMMAND_CHAINING_OPERATORS)
for command in invalid_tutor_command
]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

pip_commands_sent = commands_word_by_word.count("pip")
pip_commands_found = command_error.value.args[0].split(" ").count("pip")

assert pip_commands_sent == pip_commands_found


def test_misspelled_tutor_command():
"""
Test running misspelled Tutor commands.

This test verifies that is warned the user of trying to execute
a misspelled Tutor command.
"""
# Given
misspelled_commands = [
"totur command 1",
"totur command 2",
"totur command 3",
"totur command 4",
"totur command 5",
]

with pytest.raises(CommandError) as command_error:
tutor_commands_manager = TestTutorCommandManager()
CommandsRunner(
commands_manager=tutor_commands_manager, commands=misspelled_commands
)

assert command_error.type is CommandError

splitted_commands = [
split_string(command, COMMAND_CHAINING_OPERATORS)
for command in misspelled_commands
]
commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ")

misspelled_commands_sent = commands_word_by_word.count("totur")
misspelled_commands_found = command_error.value.args[0].split(" ").count("totur")

assert misspelled_commands_sent == misspelled_commands_found
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Test tutor commands functions.
"""

from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager


class TestTutorCommandManager(TutorCommandManager):
"""
Executes a Tutor command for testing.

This class provides functionality to execute extra Tutor commands for testing.

Args:
CommandManager (class): Base command manager class.
"""
commands_ran = 0

def run_command(self, command: str):
"""
This method runs an testing command.

Args:
command (str): Testing command.
"""

self.commands_ran += 1
2 changes: 2 additions & 0 deletions tutordistro/commands/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from tutordistro.commands.enable_private_packages import enable_private_packages
from tutordistro.commands.enable_themes import enable_themes
from tutordistro.commands.repository_validator import repository_validator
from tutordistro.commands.run_extra_commands import run_extra_commands
from tutordistro.commands.syntax_validator import syntax_validator


Expand All @@ -23,3 +24,4 @@ def distro() -> None:
distro.add_command(enable_private_packages)
distro.add_command(repository_validator)
distro.add_command(syntax_validator)
distro.add_command(run_extra_commands)
32 changes: 32 additions & 0 deletions tutordistro/commands/run_extra_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Distro run extra commands command.
"""

import subprocess

import click
from tutor import config as tutor_config

from tutordistro.distro.extra_commands.application.commands_runner import CommandsRunner
from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager


@click.command(name="run-extra-commands", help="Run tutor commands")
def run_extra_commands():
"""
This command runs tutor commands defined in DISTRO_EXTRA_COMMANDS
"""
directory = (
subprocess.check_output("tutor config printroot", shell=True)
.decode("utf-8")
.strip()
)
config = tutor_config.load(directory)
distro_extra_commands = config.get("DISTRO_EXTRA_COMMANDS", None)

tutor_commands_manager = TutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=distro_extra_commands)

if distro_extra_commands:
for command in distro_extra_commands:
run_tutor_command(command=command)
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Distro command runner.
"""
# Was necessary to use this for compatibility with Python 3.8
from typing import List, Optional

from tutordistro.distro.extra_commands.domain.command_manager import CommandManager


class CommandsRunner:
"""
Command runner.

This class is responsible of executing extra commands by invoking the run_command method
on a commands manager.

Attributes:
commands_manager (ThemeRepository): The command manager to use for executing the extra command.
"""

def __init__(self, commands_manager: CommandManager, commands: Optional[List[str]]):
self.commands_manager = commands_manager

if commands is not None:
commands_manager.validate_commands(commands)

def __call__(self, command: str):
"""
Run the provided command.

This method runs the provided command by invoking the run_command method
from the given command manager

Args:
command (str): Command to execute.
"""

return self.commands_manager.run_command(command=command)
Empty file.
12 changes: 12 additions & 0 deletions tutordistro/distro/extra_commands/domain/command_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Command Manager"""

import abc
from abc import abstractmethod


class CommandManager(metaclass=abc.ABCMeta):
"""Command Manager"""

@abstractmethod
def run_command(self, command: str):
"""Run a command."""
Empty file.
Loading
Loading