diff --git a/roles/devserver/userland-software/files/shell.py b/roles/devserver/userland-software/files/shell.py new file mode 100755 index 0000000..0460f85 --- /dev/null +++ b/roles/devserver/userland-software/files/shell.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 + +# ------------------------------------------------------------- +# Operations utilities +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Project: Nasqueron +# Author: Sébastien Santoro aka Dereckson +# Created: 2018-03-08 +# License: BSD-2-Clause +# Source file: roles/devserver/userland-software/files/shell.sh +# ------------------------------------------------------------- +# +# +# This file is managed by our rOPS SaltStack repository. +# +# Changes to this file may cause incorrect behavior +# and will be lost if the state is redeployed. +# + +from collections import deque + +import os +import re +import subprocess +import sys +import yaml + + +# ------------------------------------------------------------- +# Configuration file locator +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def get_candidates_configuration_directories(): + candidates = [] + + if 'HOME' in os.environ: + candidates.append(os.environ['HOME']) + + candidates.append('/usr/local/etc') + candidates.append('/etc') + + return candidates + + +def get_candidates_configuration_files(): + return [directory + "/.shell.yml" for directory + in get_candidates_configuration_directories()] + + +def find_configuration_file(): + for candidate in get_candidates_configuration_files(): + if os.path.isfile(candidate): + return candidate + + +# ------------------------------------------------------------- +# Configuration file parser +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def parse_configuration_file(filename): + configuration_file = open(filename, 'r') + configuration = yaml.load(configuration_file) + configuration_file.close() + + return configuration + + +# ------------------------------------------------------------- +# Server connection +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +class ServerConnection: + """Represents a server connection with a command to run.""" + + config = {} + args = [] + + def __init__(self, config, args): + self.config = config + self.args = deque(args) + + def clear_args(self): + self.args = deque([]) + + def pop_all_args(self): + to_return = list(self.args) + self.clear_args() + return to_return + + def get_default_command(self): + return ["ssh"] + + def get_alias(self, alias_name): + return self.get_config_section('aliases', alias_name) + + def get_handler(self, hander_name): + return self.get_config_section('handlers', hander_name) + + def get_config_section(self, section, key): + if section in self.config: + if key in self.config[section]: + return self.config[section][key] + + def parse_alias(self, alias): + if 'args' in alias: + self.args.extendleft(alias['args']) + + if 'handler' in alias: + handler = self.config['handlers'][alias['handler']] + return self.parse_handler(handler) + + if 'command' in alias: + return self.parse_command(alias['command']) + + raise ValueError("Unable to parse alias") + + def parse_handler(self, handler): + command = self.get_default_command() + + if 'interactive' in handler and handler['interactive']: + command.append("-t") + + command.append(handler['server']) + command.extend(self.parse_command(handler['command'])) + command.extend(self.args) + + return command + + def parse_variable_fragment(self, variable): + # {{%s-|bash}} means %s-, with bash as default value if we don't + # have any more argument to substitute + matches = re.search('(.*)\|(.*)', variable) + if matches: + if not self.args: + return [matches.group(2)] + + cleaned_fragment = matches.group(1) + return self.parse_variable_fragment(cleaned_fragment) + + # Substitute with one argument + if variable == '%s': + return [self.args.popleft()] + + # Substitute with all arguments + if variable == '%s-': + return self.pop_all_args() + + raise ValueError("Can't parse " + variable) + + def parse_fragment(self, fragment): + # If the fragment is {{something}}, this is a variable to substitute. + matches = re.search('{{(.*)}}', fragment) + if matches: + return self.parse_variable_fragment(matches.group(1)) + + return [fragment] + + def parse_command(self, command): + parsed_command = [] + + fragments = [self.parse_fragment(fragment) for fragment in command] + for fragment in fragments: + parsed_command.extend(fragment) + + return parsed_command + + def parse_connection(self): + if not self.args: + raise ValueError("Expected arguments missing") + + target = self.args.popleft() + + # Is it an alias? + alias = self.get_alias(target) + if alias is not None: + return self.parse_alias(alias) + + # Is it an handler? + handler = self.get_handler(target) + if handler is not None: + return self.parse_handler(handler) + + raise ValueError(target + ": No such target") + +# ------------------------------------------------------------- +# Runner code +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def get_program_name(): + return os.path.basename(sys.argv[0]) + + +def is_debug_mode_enabled(): + return 'DEBUG' in os.environ + + +def print_error(err): + print("{}: {}".format(get_program_name(), err), file=sys.stderr) + + +def get_configuration(): + configuration_file = find_configuration_file() + + if configuration_file is None: + print_error("No shell configuration file found") + exit(2) + + return parse_configuration_file(configuration_file) + + +def usage(): + print("usage: shell target [subtarget] [command ...]", file=sys.stderr) + + +def main(): + if len(sys.argv) < 2: + usage() + exit(1) + + config = get_configuration() + connection = ServerConnection(config, sys.argv[1:]) + try: + subprocess_args = connection.parse_connection() + except ValueError as e: + print_error(e) + exit(4) + + if is_debug_mode_enabled(): + print(subprocess_args, file=sys.stderr) + + subprocess.run(subprocess_args) + + +if __name__ == '__main__': + main() diff --git a/roles/devserver/userland-software/misc.sls b/roles/devserver/userland-software/misc.sls index 6b3a2be..5df7b90 100644 --- a/roles/devserver/userland-software/misc.sls +++ b/roles/devserver/userland-software/misc.sls @@ -1,135 +1,144 @@ # ------------------------------------------------------------- # Salt — Provision dev software # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Project: Nasqueron # Created: 2017-10-20 # License: Trivial work, not eligible to copyright # ------------------------------------------------------------- -{% from "map.jinja" import packages, packages_prefixes with context %} +{% from "map.jinja" import dirs, packages, packages_prefixes with context %} devserver_software_misc_vcs: pkg: - installed - pkgs: # VCS - cvs - fossil - subversion # Bridges - cvs2svn - {{ packages_prefixes.python2 }}hg-git devserver_software_misc_media: pkg: - installed - pkgs: - ffmpeg2theora - opencore-amr - opus - speex - speexdsp - x265 devserver_software_misc_text_processing: pkg: - installed - pkgs: - antiword - odt2txt - texlive-full devserver_software_misc_security: pkg: - installed - pkgs: - aescrypt - pwgen - vault devserver_software_misc_tools: pkg: - installed - pkgs: - boxes - cursive - fusefs-s3fs - gist - p7zip - primegen - rsync - unix2dos {% if grains['os'] == 'FreeBSD' %} - gawk {% endif %} {% if grains['os'] == 'FreeBSD' %} devserver_software_misc_ports: pkg: - installed - pkgs: - ccache - portmaster - portshaker - porttools - poudriere - portsearch portsearch_database: cmd.run: - name: portsearch -u - creates: /var/db/portsearch - require: - pkg: devserver_software_misc_ports /etc/make.conf: file.managed: - source: salt://roles/devserver/userland-software/files/make.conf freebsd_kernel_modules: pkg.installed: - pkgs: - pefs-kmod freebsd_kernel_modules_enable: module.wait: - name: freebsdkmod.load - mod: pefs - persist: True - watch: - pkg: freebsd_kernel_modules {% endif %} devserver_software_misc_p2p: pkg: - installed - pkgs: - transmission-daemon - transmission-web devserver_software_misc_gadgets: pkg: - installed - pkgs: - asciiquarium - binclock - ditaa - epte - weatherspect devserver_software_misc_games: pkg: - installed - pkgs: - bsdgames - textmaze devserver_software_misc_network: pkg: - installed - pkgs: - getdns - iftop {% if grains['os_family'] == 'Debian' %} - sockstat {% endif %} + +# ------------------------------------------------------------- +# Custom simple binaries +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +{{ dirs.bin }}/shell: + file.managed: + - source: salt://roles/devserver/userland-software/files/shell.py + - mode: 755