Skip to content

Commit

Permalink
Release 0.1.25
Browse files Browse the repository at this point in the history
- Cleanup and API refactorization.
  • Loading branch information
r-dvl authored May 20, 2024
2 parents c741e5c + aaa0600 commit eb3ce4d
Show file tree
Hide file tree
Showing 13 changed files with 74 additions and 71 deletions.
1 change: 1 addition & 0 deletions api/dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ENV TZ=Europe/Madrid
# Ansible vars
ENV ANSIBLE_INVENTORY=/ansible-playbooks/inventories/hosts.yaml
ENV ANSIBLE_PLAYBOOKS=/ansible-playbooks/playbooks
ENV ANSIBLE_SCRIPTS=/ansible-playbooks/scripts
ENV ANSIBLE_LOGS=/logs
ENV VAULT_PASSWORD_FILE=/config/vault_password_file

Expand Down
3 changes: 2 additions & 1 deletion api/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from v1.endpoints import crontab, logs, hosts, playbooks
from v1.endpoints import crontab, logs, hosts, playbooks, scripts


app = FastAPI()
Expand All @@ -18,6 +18,7 @@
app.include_router(logs.router, prefix="/v1/logs", tags=["logs"])
app.include_router(hosts.router, prefix="/v1/hosts", tags=["hosts"])
app.include_router(playbooks.router, prefix="/v1/playbooks", tags=["playbooks"])
app.include_router(scripts.router, prefix="/v1/scripts", tags=["scripts"])

if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)
2 changes: 1 addition & 1 deletion api/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ crontab /etc/cron.d/ansible
service cron start

# Start your application
python3 ./main.py
python3 ./main.py
36 changes: 12 additions & 24 deletions api/v1/endpoints/crontab.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,34 @@
from fastapi import APIRouter, HTTPException, Query
import re
from pathlib import Path
from fastapi import APIRouter, Query


router = APIRouter()

# Crontabs path
path = '/etc/cron.d'
crontab_path = Path('/etc/cron.d/ansible')

@router.get("/read")
def get_crontab(crontab: str = Query(..., description="Crontab name")):
@router.get("/")
def get_crontab():
'''
Reads a crontab file with a specific format and returns a list of tasks.
Format:
# Description
* * * * * /path/to/scripts/script.sh
* * * * * /path/to/script.sh
The function reads the specified crontab file, parses it, and returns a list of tasks.
Each task is represented as a dictionary with the following keys:
- description: A string that describes the task.
- cron_expression: A string that represents the cron expression for the task.
- task_name: The name of the task.
Parameters:
crontab (str): The name of the crontab file to read.
Returns:
dict: A dictionary with a single key "crontab" that maps to a list of tasks.
Each task is a dictionary with keys "description", "cron_expression", and "task_name".
If an error occurs, the dictionary contains a single key "error" with a string description of the error.
'''
try:
if not crontab:
raise HTTPException(status_code=400, detail="File name not specified")

with open(f"{path}/{crontab}", 'r') as f:
with open(crontab_path, 'r') as f:
crontab_output = f.read()

crontab_lines = crontab_output.strip().split("\n")
Expand All @@ -56,20 +50,14 @@ def get_crontab(crontab: str = Query(..., description="Crontab name")):

return {"crontab": crontab_data}
except FileNotFoundError:
return {"error": f"{crontab} crontab not found."}
return {"error": "Crontab not found."}
except Exception as e:
return {"error": str(e)}

@router.get("/get-task")
def get_crontab(
crontab: str = Query(..., description="Crontab name"),
task: str = Query(..., description="script to fetch")
):
def get_task(task: str = Query(..., description="Task to fetch")):
try:
if not crontab:
raise HTTPException(status_code=400, detail="File name not specified")

with open(f"{path}/{crontab}", 'r') as f:
with open(crontab_path, 'r') as f:
crontab_output = f.read()

crontab_lines = crontab_output.strip().split("\n")
Expand All @@ -79,7 +67,7 @@ def get_crontab(
if line.startswith("#"):
description = line[2:]
else:
match = re.match(r"(\S+ \S+ \S+ \S+ \S+) /home/ansible/ansible-playbooks/scripts/(.+)\.sh", line)
match = re.match(r"(\S+ \S+ \S+ \S+ \S+) /ansible-playbooks/scripts/(.+)\.sh", line)
if match:
cron_expression, task_name = match.groups()
if task in task_name:
Expand All @@ -92,6 +80,6 @@ def get_crontab(

return {"task": task_data}
except FileNotFoundError:
return {"error": f"{crontab} crontab not found."}
return {"error": "Crontab not found."}
except Exception as e:
return {"error": str(e)}
11 changes: 7 additions & 4 deletions api/v1/endpoints/hosts.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from fastapi import APIRouter
import os
import yaml
from pathlib import Path
from fastapi import APIRouter


router = APIRouter()

# Hosts path
path = '/ansible-playbooks/inventories/hosts.yaml'
hosts_path = Path(os.getenv('ANSIBLE_INVENTORY'))

@router.get("/read")
@router.get("/")
def get_hosts():
with open(path, 'r') as stream:
with open(hosts_path, 'r') as stream:
try:
data = yaml.safe_load(stream)
hosts_info = {}
Expand Down
17 changes: 9 additions & 8 deletions api/v1/endpoints/logs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from fastapi import APIRouter, Query
from fastapi.responses import FileResponse
import os
from datetime import date, datetime
from pathlib import Path
from fastapi import APIRouter, Query
from fastapi.responses import FileResponse


router = APIRouter()

# Logs Path
path = Path("/logs")
logs_path = Path(os.getenv('ANSIBLE_LOGS'))

@router.get("/get-log-content")
def get_log_content(
Expand Down Expand Up @@ -48,9 +49,9 @@ def execution_statistics(
dict: A dictionary containing execution statistics for each playbook and globally.
dict: An error message if the logs directory does not exist.
'''
if path.exists():
if logs_path.exists():
# Obtain folders inside /logs/ (Every playbook logs)
playbooks = [f for f in path.iterdir() if f.is_dir()]
playbooks = [f for f in logs_path.iterdir() if f.is_dir()]

results = {}

Expand Down Expand Up @@ -114,6 +115,7 @@ def execution_statistics(
else:
return {"error": "Path doesn't exists."}

# TODO: Number of executions to fetch as endpoint parameter
@router.get("/last-executions")
def last_executions():
'''
Expand All @@ -123,9 +125,9 @@ def last_executions():
list: A list of dictionaries, each containing information about an execution.
dict: An error message if the logs directory does not exist.
'''
if path.exists():
if logs_path.exists():
# Obtain folders inside /logs/ (Every playbook logs)
playbooks = [f for f in path.iterdir() if f.is_dir()]
playbooks = [f for f in logs_path.iterdir() if f.is_dir()]

executions = []

Expand Down Expand Up @@ -156,7 +158,6 @@ def last_executions():
})

# Order by date and time and return last 5 executions
# TODO: Number of executions to fetch as endpoint parameter
executions.sort(key=lambda x: x["datetime"], reverse=True)
last_executions = executions[:5]

Expand Down
39 changes: 10 additions & 29 deletions api/v1/endpoints/playbooks.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
from fastapi import APIRouter, HTTPException
import yaml
import os
import yaml
import glob
import re
import subprocess
from pathlib import Path
from fastapi import APIRouter


router = APIRouter()

# Playbooks path
base_path = "/ansible-playbooks/playbooks/"
scripts_path = "/ansible-playbooks/scripts/"
crontab_path = '/etc/cron.d/ansible'
playbooks_path = Path(os.getenv('ANSIBLE_PLAYBOOKS'))
crontab_path = Path('/etc/cron.d/ansible')

@router.get("/get-playbooks")
@router.get("/")
def get_playbooks():
"""
This endpoint reads all .yaml files in the specified path and checks if it is scheduled in crontab.
Each playbook is added to the 'playbooks' list.
"""
# Check if path exists
if not os.path.exists(base_path):
if not os.path.exists(playbooks_path):
return {"error": "The specified path does not exist."}

# Fetch every playbook
yaml_files = glob.glob(os.path.join(base_path, '*.yaml'))
yaml_files = glob.glob(os.path.join(playbooks_path, '*.yaml'))

playbooks = {}

Expand Down Expand Up @@ -69,22 +68,4 @@ def get_playbooks():
except yaml.YAMLError as exc:
return {"error": f"Error reading {yaml_file}."}

return playbooks

@router.post("/execute-script/{script_name}")
def execute_script(script_name: str):
"""
This endpoint executes a script located in /ansible-playbooks/scripts/.
The script name is passed as a path parameter.
"""
script_path = os.path.join(scripts_path, script_name)

# Check if script exists
if not os.path.exists(script_path):
raise HTTPException(status_code=404, detail="Script not found")

# Execute the script
result = subprocess.run(["bash", script_path], capture_output=True, text=True)

# Return the result
return {"output": result.stdout, "error": result.stderr}
return playbooks
27 changes: 27 additions & 0 deletions api/v1/endpoints/scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os
import subprocess
from pathlib import Path
from fastapi import APIRouter, HTTPException


router = APIRouter()

scripts_path = Path(os.getenv('ANSIBLE_SCRIPTS'))

@router.post("/execute-script/{script_name}")
def execute_script(script_name: str):
"""
This endpoint executes a script located in /ansible-playbooks/scripts/.
The script name is passed as a path parameter.
"""
script_path = os.path.join(scripts_path, script_name)

# Check if script exists
if not os.path.exists(script_path):
raise HTTPException(status_code=404, detail="Script not found")

# Execute the script
result = subprocess.run(["bash", script_path], capture_output=True, text=True)

# Return the result
return {"output": result.stdout, "error": result.stderr}
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
environment:
- ANSIBLE_INVENTORY=/ansible-playbooks/inventories/hosts.yaml
- ANSIBLE_PLAYBOOKS=/ansible-playbooks/playbooks
- ANSIBLE_SCRIPTS=/ansible-playbooks/scripts
- ANSIBLE_LOGS=/logs
- VAULT_PASSWORD_FILE=/config/vault_password_file

Expand Down
2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ansible-manager",
"author": "R-dVL",
"version": "0.1.21",
"version": "0.1.25",
"private": false,
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion ui/src/sections/home/view/home-view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function HomeView() {
.then(data => setLatestExecutions(data))
.catch(error => setExecutionsError(error.message));

fetch(`${apiUrl}/v1/crontab/read?crontab=ansible`)
fetch(`${apiUrl}/v1/crontab/`)
.then(response => {
if (!response.ok) {
throw new Error('Error fetching next executions');
Expand Down
2 changes: 1 addition & 1 deletion ui/src/sections/hosts/view/hosts-view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function HostsView() {
};

useEffect(() => {
fetch(`${apiUrl}/v1/hosts/read`)
fetch(`${apiUrl}/v1/hosts/`)
.then(response => {
if (!response.ok) {
throw new Error('Error fetching hosts data');
Expand Down
2 changes: 1 addition & 1 deletion ui/src/sections/playbooks/view/playbooks-view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default function PlaybooksPage() {
const notFound = !dataFiltered.length && !!filterName;

useEffect(() => {
fetch(`${apiUrl}/v1/playbooks/get-playbooks`)
fetch(`${apiUrl}/v1/playbooks/`)
.then(response => {
if (!response.ok) {
throw new Error('Error fetching playbooks data');
Expand Down

0 comments on commit eb3ce4d

Please sign in to comment.