Skip to content

Commit

Permalink
Hardware fixes (#29)
Browse files Browse the repository at this point in the history
* removed ABC

* fixed config file usage

* other config issue

* typing error, trying to concat str with Path

* update generate subcommand

* removed install_file overwrite

* added self prefix to vars in dns.py

* added additional modules

* fixed imports and some errors

* removed raised error from heading not found in yamlparser

* fixed typo

* changed seperator

* Updated Path in os file download

* changed preseed to ftp dir

* fixed directory 2

* Fixed setup commands for ftp

* Corrected false statemtn

* Added dhcp-boot option to dnsmasq

* changed directory

* Changed module call order

* added language

* added boot args, changed local_root to anon_root

* Updated jinja template for preseed prompts

* Revert "fixed directory 2"

This reverts commit f4e4db0.

* reverted preseed install dir to tftp

* removed outdated flag

* Removed invalid flag

* fixed tests

* fixed some linting errors

* more lint fixes

* linting again

* forgot to commit these

* almost done with lints

* good enough

* type issue

* guess this never got pushed

---------

Co-authored-by: HenrithicusGreenson <greeht01@pfw.edu>
  • Loading branch information
gingrm02 and HenrithicusGreenson authored Dec 7, 2023
1 parent f2e1c1d commit 258031a
Show file tree
Hide file tree
Showing 20 changed files with 156 additions and 184 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ jobs:
- name: Install dependencies
run: poetry install
- name: Analysing the code with pylint
run: poetry run pylint $(git ls-files '*.py')
run: poetry run pylint --fail-under=9 $(git ls-files '*.py')

2 changes: 1 addition & 1 deletion documentation/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Network:
gateways: "10.0.0.50"
dns-servers: "1.1.1.1"
no-dns: false
FTP:
ftp:
directory: "/ftp"
ftp-port: 20
OS:
Expand Down
25 changes: 14 additions & 11 deletions genisys/configParser.py → genisys/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from typing_extensions import Self

class YAMLParser:
"""Parses a YAML config file and provides helper methods to access it's contents"""
def __init__(self, filename) -> None:
self.filename = filename
# end __init__

def getSection(self: Self, heading: str) -> dict:
def get_section(self: Self, heading: str) -> dict:
'''Returns all of the key value pairs under a specific heading as a Python dictionary'''
dictionary = {}
with open(self.filename, encoding='utf-8') as file:
Expand All @@ -22,12 +23,12 @@ def getSection(self: Self, heading: str) -> dict:
print("Heading is empty.")

else:
raise Exception('Heading not found.')
return {}

return dictionary
# end getSection
# end get_section

def getAllHeadings(self: Self) -> list:
def get_all_headings(self: Self) -> list:
''' Returns a list object containing all section headings in the provided YAML file '''
headings = []
with open(self.filename, encoding='utf-8') as file:
Expand All @@ -37,20 +38,22 @@ def getAllHeadings(self: Self) -> list:
headings.append(heading)

return headings
# end getAllHeadings
# end get_all_headings

def printDict(self: Self, dictionary) -> None:
def print_dict(self: Self, dictionary) -> None:
"""Helper function to pretty print a dictionary"""
for key in dictionary:
print(f"{key}: {dictionary[key]}")
# end printDict

# end YAMLParser

def main():
parser = YAMLParser('example.yml')
print(parser.getAllHeadings())
for eachSection in parser.getAllHeadings():
parser.printDict(parser.getSection(eachSection))
"""Load a test config and parse it"""
parser = YAMLParser('example.yml')
print(parser.get_all_headings())
for each_section in parser.get_all_headings():
parser.print_dict(parser.get_section(each_section))

if __name__ == '__main__':
main()
113 changes: 39 additions & 74 deletions genisys/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,67 @@

import argparse
import subprocess
import genisys.modules.netplan as net
import genisys.modules.preseed as ps
import genisys.modules.nat as nt
import genisys.modules.kernelparameter as kp
import genisys.configParser as cp


def validate(modules):
for module in modules:
if not module.validate():
from genisys.modules.netplan import Netplan
from genisys.modules.preseed import Preseed
from genisys.modules.nat import Nat
from genisys.modules.kernelparameter import KernelParameter
from genisys.modules.dns import Dnsmasq
from genisys.modules.ftp import VsftpdModule
from genisys.modules.os_file_download import OSDownload
from genisys.modules.syslinux import Syslinux
import genisys.config_parser as cp

MODULES = [OSDownload, Netplan, Preseed, Nat, KernelParameter, Dnsmasq, VsftpdModule, Syslinux]

def validate(file):
"""Display validation errors to the user."""
for module in MODULES:
mod = module(file)
if not mod.validate():
print(f"Error in {module.__class__.__name__} configuration!")
else:
print(f"{module.__class__.__name__} configuration is valid!")


def install_config(file, root="/"):
"""Install all genisys files to their specified directories
and run any setup commands they have
"""
print(f"Installing config file: {file} with root at {root}")
# netplan
netplan = net.Netplan(file)
netplan.install(root)

# preseed
preseed = ps.Preseed(file)
preseed.install(root)

# nat
nat = nt.Nat(file)
nat.install(root)

# kernelparameter
kernelParameter = kp.KernelParameter(file)
kernelParameter.install(root)

for module in MODULES:
mod = module(file)
mod.install(root)
for command in mod.setup_commands():
subprocess.run(command, check=False)

def generate_config(file, root="."):
"""Generate all genisys files and save them to the specified directory"""
print(f"Generating config file: {file} with root at {root}")
# netplan
netplan = net.Netplan(file)
netplan.generate()

# preseed
preseed = ps.Preseed(file)
preseed.generate()

# nat
nat = nt.Nat(file)
nat.generate()

# kernelparameter
kernelParameter = kp.KernelParameter(file)
kernelParameter.generate()

for module in MODULES:
mod = module(file)
mod.install(root)

def daemon():
"""Monitor the config file for changes"""
print("Starting daemon...")

raise NotImplementedError
# TODO: Implement the daemon logic here


def run(subcommand, args, module):
def run(subcommand, args):
"""Parse command line options and run the relevant helper method"""
# Config Parser
yamlParser = cp.YAMLParser(args.file)

# netplan
netplan = net.Netplan(yamlParser)

# preseed
preseed = ps.Preseed(yamlParser)

# nat
nat = nt.Nat(yamlParser)

# kernelparameter
kernelParameter = kp.KernelParameter(yamlParser)

modulesList = [netplan, preseed, nat, kernelParameter]
yaml_parser = cp.YAMLParser(args.file)

if subcommand == "validate":
validate(module)
validate(yaml_parser)
elif subcommand == "install":
install_config(yamlParser, args.root)
# setup commands
for mod in modulesList:
setup = mod.setup_commands()
# function setup_commands returns list
for command in setup:
subprocess.run(command, check=False)
install_config(yaml_parser, args.root)
elif subcommand == "generate":
generate_config(yamlParser, args.root)
generate_config(yaml_parser, args.root)


def main():
"""Parse the command line options"""
parser = argparse.ArgumentParser(description="Config File Management Tool")

# Subcommands
Expand All @@ -119,7 +87,7 @@ def main():
"-f",
"--file",
type=str,
default="default_config.cfg",
default="/etc/genisys.yaml",
help="Specify input configuration file.",
)

Expand All @@ -139,10 +107,7 @@ def main():

args = parser.parse_args()

# TODO: Instantiate modules here
modules = [] # Example: modules = [NetworkModule(), FirewallModule()]

run(args.command, args, modules)
run(args.command, args)


if __name__ == "__main__":
Expand Down
10 changes: 3 additions & 7 deletions genisys/modules/base.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from pathlib import Path
from abc import ABCMeta, abstractmethod
from typing_extensions import Self, Union, List

class Module(metaclass=ABCMeta):
class Module:
"""Base class all module should inherit from"""
@abstractmethod
def generate(self: Self) -> str:
"""Generates the content of the configuration file."""

raise NotImplementedError
#end generate

@abstractmethod
def install_location(self: Self) -> Path:
"""Returns the location that the config file should be installed to.
This path should always be absolute. Relative paths will be assumed
Expand All @@ -25,15 +22,14 @@ def install(self: Self, chroot: Union[Path, str] = Path('/')):
"""Default implementation of the installation procedure. Without chroot
this will likely require the application is ran as root.
"""

# treat all install_locations as relative
if self.install_location().is_absolute():
install_file = Path(chroot, *self.install_location().parts[1:])
else:
install_file = Path(chroot, self.install_location())

# backup any existing files
install_file = Path(chroot, self.install_location())
if install_file.exists():
install_file.rename(install_file.with_suffix(install_file.suffix + '.bak'))

Expand All @@ -52,7 +48,7 @@ def validate(self: Self) -> bool:
try:
self.generate()
return True
except:
except (KeyError, ValueError):
return False
#end validate

Expand Down
66 changes: 31 additions & 35 deletions genisys/modules/dns.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,50 @@
from genisys.modules import base
from typing import Self, Union, List
from pathlib import Path
from jinja2 import Template
import subprocess
from typing_extensions import Self, Union, List
from genisys.modules import base

class Dnsmasq(base.Module):
"""Creates the nessecary DNS, DHCP, and TFTP configurations to PXE boot clients"""
DNS_DIR = '/etc'
DNS_FILE = 'dnsmasq.conf'

def __init__(self: Self, config):
"""Pulling DNSMasq config information from the Network header in the config yaml file"""
self.config["network"] = config.getSection("Network")
self.config = {}
self.config["network"] = config.get_section("Network")
#Adding override config to read the last field in our config file
self.config["overrides"] = config.getSection("DNSMasq Overrides")
self.config["overrides"] = config.get_section("DNSMasq Overrides")

def install_location(self: Self) -> Path:
"""This is where the DNSMasq config file is stored/accessed"""
return Path(DNS_DIR, DNS_FILE)
return Path(self.DNS_DIR, self.DNS_FILE)

def generate(self: Self) -> str:
configWriter = ''
#The below line deals with the port 53 resolved issue when running dnsmasq after installation
configWriter+="bind-interfaces\n"
config_writer = ''

# The below line deals with the port 53 resolved issue when running dnsmasq
config_writer+="bind-interfaces\n"
if 'interface' in self.config["network"]:
configWriter+=("interface=" + self.config['network']['interface'] + "\n")
config_writer+=("interface=" + self.config['network']['interface'] + "\n")
if 'no-dhcp' in self.config["network"]:
if self.config['network']['no-dhcp'] == 'false':
if 'dhcp-ranges' in self.config['network'] and 'dhcp-lease' in self.config['network']:
configWriter+=("dhcp-range=" + self.config['network']['dhcp-ranges'] + "," + self.config['network']['dhcp-lease'] + "\n")
configWriter+="enable-tftp\n"
if 'tftp_directory' in self.config['netowrk']:
configWriter+=("tftp-root=" + self.config['network']['tftp_directory'] + "\n")
if not self.config['network']['no-dhcp']:
if 'dhcp-ranges' in self.config['network'] \
and 'dhcp-lease' in self.config['network']:
config_writer+=(f'dhcp-boot={self.config["network"]["tftp_directory"]}/pxelinux.0\n')
config_writer+=(f'dhcp-range={self.config['network']['dhcp-ranges']},{self.config['network']['dhcp-lease']}\n')
config_writer+="enable-tftp\n"
if 'tftp_directory' in self.config['network']:
config_writer+=("tftp-root=" + self.config['network']['tftp_directory'] + "\n")
if 'dns-servers' in self.config['network']:
configWriter+=("server=" + self.config['network']['dns-servers'] + "\n")
config_writer+=("server=" + self.config['network']['dns-servers'] + "\n")
#adding potential future logic for disabling only dns below
# if 'no-dns' in self.config:
# We add the following string: DNSMASQ_EXCEPT=lo to the file /etc/default/dnsmasq
# We add the following string: DNSMASQ_EXCEPT=lo to the file /etc/default/dnsmasq
if 'authoritative' in self.config['overrides']:
if self.config['overrides']['authoritative'].lower() == 'true':
configWriter+="dhcp-authoritative\n"
if self.config['overrides']['authoritative']:
config_writer+="dhcp-authoritative\n"
else:
configWriter+="#dhcp-authoritative\n"
return configWriter
config_writer+="#dhcp-authoritative\n"
return config_writer

def setup_commands(self: Self) -> Union[List[str], List[List[str]]]:
return [["systemctl", "restart", "dnsmasq"]]
def validate(self: Self) -> bool:
"""Validates the configuration by attempting to generate the configuration file."""
try:
self.generate()
#The following logic is supposed to use dnsmasq inbuitl validation dnsmasq --test which returns dnsmasq: syntax check OK. when valid
#For some reason, this code cannot check the output of the command to verify that the config file passed the test
#valid = subprocess.check_output(["sudo", "dnsmasq", "--test"], stdout=subprocess.PIPE)
#validString = valid.stdout.decode('utf-8')
#if validString
return True
except:
return False
6 changes: 3 additions & 3 deletions genisys/modules/ftp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class VsftpdModule(Module):

def __init__(self, config):
# Obtain the 'Network' section from the configuration
network_config = config.getSection("Network")
network_config = config.get_section("Network")

# Check and obtain the 'ftp' section from the 'Network' configuration
if "ftp" not in network_config:
Expand Down Expand Up @@ -43,7 +43,7 @@ def generate(self) -> str:
"use_localtime=YES",
"pasv_enable=YES",
f"listen_port={ftp_port}",
f"local_root={directory}",
f"anon_root={directory}",
f"listen_address={bind_addr}",
]
# Joins all configuration lines into a single string separated by newline characters
Expand All @@ -55,4 +55,4 @@ def install_location(self) -> Path:

def setup_commands(self) -> List[str]:
"""Returns a list of shell commands to set up the vsftpd service."""
return ["systemctl restart vsftpd.service"]
return [["systemctl", "restart", "vsftpd.service"]]
2 changes: 1 addition & 1 deletion genisys/modules/kernelparameter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path
from typing_extensions import Self, Union, List
from genisys.modules import base
from genisys.configParser import YAMLParser
from genisys.config_parser import YAMLParser

class KernelParameter(base.Module):
'''99 prefix guarantees that this rule will overwrite sysctl.conf parameter assignment,
Expand Down
Loading

0 comments on commit 258031a

Please sign in to comment.