Skip to content

Commit

Permalink
feat: scenario init expansion
Browse files Browse the repository at this point in the history
Signed-off-by: Piotr <piotrzan@gmail.com>
  • Loading branch information
Piotr1215 committed May 28, 2024
1 parent 6797d94 commit ff72d30
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 19 deletions.
2 changes: 1 addition & 1 deletion killercoda_cli/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
#
# SPDX-License-Identifier: MIT
# This is setup in build process in CI, but also servers as a record for the --version flag
__version__ = "1.0.3"
__version__ = "1.0.8"
18 changes: 1 addition & 17 deletions killercoda_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
from typing import List, Optional
from killercoda_cli.__about__ import __version__
from killercoda_cli.scenario_init import init_project

class FileOperation:
"""
Expand Down Expand Up @@ -369,23 +370,6 @@ def execute_file_operations(file_operations):
elif operation.operation == "rename":
os.rename(operation.path, operation.content)

def init_project():
"""initialize a new project by creating index.json file"""
if os.path.exists("index.json"):
print("The 'index.json' file already exists. Please edit the existing file.")
return

index_data = {
"details": {
"title": "Project Title",
"description": "Project Description",
"steps": [],
}
}
with open("index.json", "w") as index_file:
json.dump(index_data, index_file, ensure_ascii=False, indent=4)
print("Project initialized successfully. Please edit the 'index.json' file to add steps.")

def main():
"""
This function orchestrates the entire process of adding a new step to the scenario,
Expand Down
113 changes: 113 additions & 0 deletions killercoda_cli/scenario_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import json
import os
import inquirer

# Define environment and backend options
environments = {
"ubuntu": "Ubuntu 20.04 with Docker and Podman",
"ubuntu-4GB": "Ubuntu 20.04 with Docker and Podman, 4GB environment",
"kubernetes-kubeadm-1node": "Kubeadm cluster with one control plane, taint removed, ready to schedule workload, 2GB environment",
"kubernetes-kubeadm-1node-4GB": "Kubeadm cluster with one control plane, taint removed, ready to schedule workload, 4GB environment",
"kubernetes-kubeadm-2nodes": "Kubeadm cluster with one control plane and one node, ready to schedule workload, 4GB environment"
}

backends = {
"kubernetes-kubeadm-1node": "Kubernetes kubeadm 1 node",
"kubernetes-kubeadm-2nodes": "Kubernetes kubeadm 2 nodes",
"ubuntu": "Ubuntu 20.04"
}

time_choices = [f"{i} minutes" for i in range(15, 50, 5)]
difficulty_choices = ["beginner", "intermediate", "advanced"]

def get_value(prompt, value_type, choices=None):
if choices:
question = [inquirer.List('choice', message=prompt, choices=choices)]
answer = inquirer.prompt(question)
return answer['choice']
else:
if value_type == "boolean":
question = [inquirer.Confirm('confirm', message=prompt, default=True)]
answer = inquirer.prompt(question)
return answer['confirm']
else:
question = [inquirer.Text('value', message=prompt)]
answer = inquirer.prompt(question)
return answer['value']

def populate_schema(schema, parent_key=""):
result = {}
for key, value_type in schema.items():
full_key = f"{parent_key}.{key}" if parent_key else key
if isinstance(value_type, dict):
result[key] = populate_schema(value_type, full_key)
elif isinstance(value_type, list):
if full_key == "details.steps" or full_key == "details.assets.host01":
result[key] = [] # Initialize as empty list
else:
result[key] = []
while True:
add_item = get_value(f"Do you want to add an item to the list '{full_key}'?", "boolean")
if add_item:
result[key].append(populate_schema(value_type[0], full_key))
else:
break
else:
if full_key == "time":
result[key] = get_value(f"Select value for {full_key}", value_type, time_choices)
elif full_key == "difficulty":
result[key] = get_value(f"Select value for {full_key}", value_type, difficulty_choices)
elif full_key == "backend.imageid":
result[key] = get_value(f"Select value for {full_key}", value_type, backends)
else:
result[key] = get_value(f"Enter value for {full_key}", value_type)
return result

def init_project():
"""initialize a new project by creating index.json file"""
if os.path.exists("index.json"):
print("The 'index.json' file already exists. Please edit the existing file.")
return

schema = {
"title": "string",
"description": "string",
"difficulty": "string",
"time": "string",
"details": {
"steps": [
{}
],
"assets": {
"host01": [
{}
]
}
},
"backend": {
"imageid": "string"
}
}

populated_data = populate_schema(schema)

# Set static values for intro and finish
populated_data["details"]["intro"] = {"text": "intro.md"}
populated_data["details"]["finish"] = {"text": "finish.md"}

if get_value("Do you want to enable Theia?", "boolean"):
populated_data["interface"] = {"layout": "ide"}

with open("index.json", "w") as index_file:
json.dump(populated_data, index_file, ensure_ascii=False, indent=4)

# Create intro.md and finish.md if they don't exist
if not os.path.exists("intro.md"):
with open("intro.md", "w") as intro_file:
intro_file.write("# Introduction\n")

if not os.path.exists("finish.md"):
with open("finish.md", "w") as finish_file:
finish_file.write("# Finish\n")

print("Project initialized successfully. Please edit the 'index.json' file to add steps.")
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ description = 'A CLI helper for writing killercoda scenarios and managing steps'
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
dependencies = [
"inquirer",
]
keywords = []
authors = [
{ name = "Piotr Zaniewski", email = "piotrzan@gmail.com" },
Expand All @@ -24,7 +27,6 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = []

[project.urls]
Documentation = "https://github.com/unknown/killercoda-cli#readme"
Expand Down
109 changes: 109 additions & 0 deletions tests/test_scenario_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import unittest
from unittest import mock
from unittest.mock import patch
from killercoda_cli import scenario_init

class TestScenarioInit(unittest.TestCase):

@patch("inquirer.prompt")
@patch("os.path.exists", return_value=False)
@patch("builtins.open", new_callable=mock.mock_open)
@patch("json.dump")
def test_init_project(self, mock_json_dump, mock_open, mock_exists, mock_prompt):
mock_prompt.side_effect = [
{'value': 'Project Title'},
{'value': 'Project Description'},
{'choice': 'beginner'},
{'choice': '15 minutes'},
{'choice': 'kubernetes-kubeadm-1node'},
{'confirm': True}
]

scenario_init.init_project()

expected_data = {
"title": "Project Title",
"description": "Project Description",
"difficulty": "beginner",
"time": "15 minutes",
"details": {
"intro": {"text": "intro.md"},
"finish": {"text": "finish.md"},
"steps": [],
"assets": {"host01": []}
},
"backend": {"imageid": "kubernetes-kubeadm-1node"},
"interface": {"layout": "ide"}
}

# Check that the index.json file was created with the expected data
calls = [
mock.call("index.json", "w"),
mock.call("intro.md", "w"),
mock.call("finish.md", "w")
]
mock_open.assert_has_calls(calls, any_order=True)
mock_json_dump.assert_called_once_with(expected_data, mock.ANY, ensure_ascii=False, indent=4)

@patch("os.path.exists", return_value=True)
@patch("builtins.print")
def test_init_project_existing_index(self, mock_print, mock_exists):
scenario_init.init_project()
mock_print.assert_called_once_with("The 'index.json' file already exists. Please edit the existing file.")

@patch("os.path.exists", side_effect=lambda path: path == "intro.md")
@patch("builtins.open", new_callable=mock.mock_open)
@patch("inquirer.prompt")
def test_init_project_create_finish_md(self, mock_prompt, mock_open, mock_exists):
mock_prompt.side_effect = [
{'value': 'Project Title'},
{'value': 'Project Description'},
{'choice': 'beginner'},
{'choice': '15 minutes'},
{'choice': 'kubernetes-kubeadm-1node'},
{'confirm': True}
]

scenario_init.init_project()
mock_open.assert_any_call("finish.md", "w")
mock_open().write.assert_any_call("# Finish\n")

@patch("os.path.exists", side_effect=lambda path: path == "finish.md")
@patch("builtins.open", new_callable=mock.mock_open)
@patch("inquirer.prompt")
def test_init_project_create_intro_md(self, mock_prompt, mock_open, mock_exists):
mock_prompt.side_effect = [
{'value': 'Project Title'},
{'value': 'Project Description'},
{'choice': 'beginner'},
{'choice': '15 minutes'},
{'choice': 'kubernetes-kubeadm-1node'},
{'confirm': True}
]

scenario_init.init_project()
mock_open.assert_any_call("intro.md", "w")
mock_open().write.assert_any_call("# Introduction\n")

@patch("os.path.exists", return_value=False)
@patch("builtins.open", new_callable=mock.mock_open)
@patch("json.dump")
@patch("inquirer.prompt")
def test_init_project_files_created(self, mock_prompt, mock_json_dump, mock_open, mock_exists):
mock_prompt.side_effect = [
{'value': 'Project Title'},
{'value': 'Project Description'},
{'choice': 'beginner'},
{'choice': '15 minutes'},
{'choice': 'kubernetes-kubeadm-1node'},
{'confirm': True}
]

scenario_init.init_project()
mock_open.assert_any_call("intro.md", "w")
mock_open().write.assert_any_call("# Introduction\n")
mock_open.assert_any_call("finish.md", "w")
mock_open().write.assert_any_call("# Finish\n")

if __name__ == "__main__":
unittest.main()

0 comments on commit ff72d30

Please sign in to comment.