Skip to content

Commit

Permalink
feat: add command to run extra tutor commands (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
bra-i-am authored Apr 5, 2024
1 parent dfcaeb3 commit 98eb210
Show file tree
Hide file tree
Showing 20 changed files with 468 additions and 0 deletions.
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.
38 changes: 38 additions & 0 deletions tutordistro/distro/extra_commands/application/commands_runner.py
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

0 comments on commit 98eb210

Please sign in to comment.