def up_cmd( ctx, provider, box, hostname, guest_os, ram_size, cpus, drive_size, machine_type, sync_dirs, ec2_security_groups, sync_type, ports, project_dir, provision, command, destroy_on_error, vm_name, ): """Start a VM or container. Will create one and begin provisioning it if it did not already exist. Accepts many options to set aspects of your VM. Precedence is CLI > Config > Env Var > defaults. """ if not os.path.isfile(f"{PROJECT_NAME}.conf"): utils.abort( f"Config file {PROJECT_NAME}.conf must be present in working directory.\n" "A config file is automatically created when you run \n" "createproject. You can also make a config file manually.") app.up(ctx, **ctx.params)
def scp(ctx=None, locations=None, **params): """Transfer file or dir with scp. This makes use of the vagrant-scp plugin, which allows for simplified args. """ if not ctx: # Using API. Else handled by cli. _set_init_vars(params.get("cwd"), params.get("tmpdir")) _set_vagrant_vars(params.get("vagrant_cwd"), params.get("vagrant_dotfile_path")) if len(locations) != 2: utils.abort( "There needs to be exactly two arguments for scp, a 'from' location " "and a 'to' location.\nYou gave: %s." % " ".join(locations)) copy_from = locations[0] copy_to = locations[1] if ":" in copy_from: # copy_from is remote, fix copy_to which is local copy_to = os.path.abspath(copy_to) else: # if no ':' in copy_from, copy_to must be remote, fix copy_from which is local copy_from = os.path.abspath(copy_from) locations = [copy_from, copy_to] vagrant_general_command("{} {}".format("scp", " ".join(locations)))
def install_config(ctx=None, output_path=None, **kwargs): """Install config file. Agrs: ctx (object): Click Context object. output_path (path): Path to place conf file. """ if not ctx: # Using API. Else handled by cli. _set_init_vars(kwargs.get("cwd"), kwargs.get("tmpdir")) if not output_path: output_path = get_env_var("cwd") path = os.path.join(output_path, "%s.conf" % PROJECT_NAME) if os.path.exists(path): utils.abort("%s.conf already esists." % PROJECT_NAME) else: with open(path, "w") as f: f.write("""\ [up] provider = virtualbox box = ubuntu/bionic64 sync_dirs = [["saltstack/etc", "/etc/salt"], ["saltstack/srv", "/srv"]] """) utils.echo("Created config at %s" % path)
def createproject(project_name, cwd, tmpdir, config_only=None, ctx=None): """Create project with basic configuration files. Agrs: project_name (path): Place to create a new project. Must be non-existing dir. config_only (bool): Determins if we should only place a conf file in the new project. """ # initialize paths _set_env() cwd = _set_cwd(cwd) path = os.path.join(cwd, project_name) _set_tmpdir(path) # create new project dir try: os.makedirs(path) # Make parent dirs if needed. except FileExistsError: utils.abort("Directory already exists.") utils.echo('Created %s project "%s" in %s.' % (PROJECT_NAME.capitalize(), project_name, path)) # Fill project dir with basic configs. install_config(ctx, output_path=path) install_gitignore(ctx, output_path=path) if not config_only: export("saltstack", path) install_auth(ctx, output_path=path)
def export(resource=None, export_path=None, force=None): """Drop default code in the CWD / user defined space. Operate on saltstack and vagrant resources. Agrs: resource (str): Resource to export: saltstack or vagrant. export_path (path): Dir to export resources to. force (bool): Determins if we should overwrite and merge conflicting files in the target path. """ if export_path: output_dir = os.path.normpath(export_path) else: output_dir = os.getcwd() if resource in ("vagrant", "saltstack"): srcs = [os.path.normpath(os.path.join(PROJECT_LOCATION, resource))] dsts = [os.path.join(output_dir, resource)] if resource == "vagrant": srcs.append( os.path.normpath(os.path.join(PROJECT_LOCATION, "settings.json"))) srcs.append( os.path.normpath(os.path.join(PROJECT_LOCATION, "Vagrantfile"))) dsts.append(os.path.join(output_dir, "settings.json")) dsts.append(os.path.join(output_dir, "Vagrantfile")) if not force: try: for path in dsts: if os.path.exists(path): click.confirm( "One or more destination files or directories in " "'%s' already exists. Attempt to merge and " "overwrite?" % dsts, abort=True, ) break # We only need general confirmation of an overwrite once. except UnboundLocalError: # dsts referenced before assignement utils.abort("The resource '%s' is not a valid option." % resource) for src, dst in zip(srcs, dsts): try: copy_tree(src, dst) # Merge copy tree with overwrites. except DistutilsFileError: # It's a file, not a dir. try: shutil.copy(src, dst) # Copy file with overwrites. except FileNotFoundError: os.makedirs( os.path.dirname(dst), exist_ok=True) # Make parent dirs if needed. # Py 3.2+ shutil.copy(src, dst) # Copy file with overwrites. utils.echo("Done exporting %s code." % resource)
def machine_type_option(machine_type=None, provider=None): """Validate machine_type. If not supplied, set to default. Set as env var. Args: machine_type (str): Machine type to use for cloud providers. provider (str): Provider to use. Return machine_type (str) """ if machine_type: if provider in ("docker", "lxc", "virtualbox"): msg = ("You have selected a machine-type, but are not using\n" "a cloud provider. You selected %s with %s.\n" % (machine_type, provider)) utils.abort(msg) set_env_var("machinetype", machine_type) return machine_type
def ec2(**params): """EC2 specific preparation for Vagrant. Setting and validating env vars. Set env vars: ami Sanitize against non-whitelist ramsize. """ if get_env_var("guest_os"): # only set during `up` set_env_var("ami", SETTINGS["GUEST_OSES"][get_env_var("guest_os")]["ec2"]) if get_env_var("ramsize") not in SETTINGS["SIZES"]: abort( "Sorry, we really need a RAM size from our whitelist for " "digitalocean. \nThe only way around that is if you specify " "a machine-type like m3.medium." ) if params.get("ec2_security_groups"): set_env_var("ec2_security_groups", params["ec2_security_groups"])
def provider_option(provider=None): """Validate provider. If not supplied, set to default. Set as env var. Args: provider (str): Provider to use. Return provider (str) """ if not provider: provider = SETTINGS["PROVIDERS_DEFAULT"] set_env_var("provider", provider) if provider not in SETTINGS["PROVIDERS"]: msg = ( 'Provider "%s" is not in the provider list.\n' "Did you have a typo? Here is as list of avalible providers:\n\n" % provider) for supported_provider in SETTINGS["PROVIDERS"]: msg = msg + "%s\n" % supported_provider utils.abort(msg) return provider
def digitalocean(): """DigitalOcean specific preparation for Vagrant. Setting and validating env vars. Set env vars: do_image Sanitize against non-whitelist ramsize and drivesize. """ if get_env_var("guest_os"): # only set during `up` set_env_var("do_image", SETTINGS["GUEST_OSES"][get_env_var("guest_os")]["do"]) if get_env_var("ramsize") not in SETTINGS["SIZES"]: abort( "Sorry, we really need a RAM size from our whitelist for " "digitalocean. \nThe only way around that is if you specify " "a machine-type like s-8vcpu-32gb." ) if get_env_var("drivesize") not in SETTINGS["SIZES"].values(): abort( "Sorry, we really need a drive size from our whitelist for " "digitalocean. \nThe only way around that is if you specify " "a machine-type like s-8vcpu-32gb." )
def sync_dirs_option(sync_dirs=None): """Validate sync_dirs. If not supplied, set to default. Set as env var. sync_dirs must be list of lists of form: `"[['guest_dir', 'host_dir'], ['guest_dir2', 'host_dir2']]"` Args: sync_dirs: Paths to sync into VM, supplied as list of lists. Return sync_dirs (list) """ if not sync_dirs: return None try: sync_dirs = ast.literal_eval(sync_dirs) except SyntaxError: utils.abort("sync_dirs cannot be evaluated as valid Python.") if type(sync_dirs) is not list: utils.abort( f"sync_dirs was not evaluated as a Python list, but as '{type(sync_dirs)}'" ) for sd in sync_dirs: if type(sd) is not list: utils.abort( f"sync_dirs element {sd} was not evaluated as a Python list, but as " f"'{type(sd)}'") # Normalize source dirs. Target Dirs must be absolute / handled by Vagrant. sync_dirs = [[os.path.realpath(os.path.expanduser(lst[0])), lst[1]] for lst in sync_dirs] set_env_var("sync_dirs", sync_dirs) return sync_dirs
def ports_option(ports=None): """Validate ports. If not supplied, set to default. Set as env var. ports must be list of lists of form: `[['guest_port', 'host_port'], ['guest_port2', 'host_port2']]` Args: ports: Paths to sync into VM, supplied as list of lists. Return ports (list) """ if not ports: return None try: ports = ast.literal_eval(ports) except SyntaxError: utils.abort("ports cannot be evaluated as valid Python.") if type(ports) is not list: utils.abort( f"`ports` was not evaluated as a Python list, but as '{type(ports)}'." ) for port_pair in ports: if type(port_pair) is not list: utils.abort( f"`ports` element {port_pair} was not evaluated as a Python list, but as " f"'{type(port_pair)}'.") if len(port_pair) != 2: utils.abort( f"Not the right number of ports to forward in {port_pair}.") for port in port_pair: if type(port_pair) != int and not 0 < port < 65535: utils.abort( f"{port} in `ports` is not an int in a valid port range.") set_env_var("ports", ports) return ports
def up(ctx=None, **params): """Start a VM / container with `vagrant up`. All str args can also be set as an environment variable; arg takes precedence. Agrs: ctx (object): Click Context object. Used to detect if CLI is used. params (dict): Dict of all args passed to `up`. In params, this looks for: provider (str): Provider to use. box (str): Vagrant box to use. cpus (int): Number of CPUs to give VirtualBox VM. guest_os (str): Guest OS to use. ram_size (int): RAM in MB to use. drive_size (int): Drive size in GB to use. machine_type (str): Machine type to use for cloud providers. sync_dirs (path): Paths to sync into VM. sync_type (str): Type of syncing to use. ports (str): Ports to forward. provision (bool): vagrant provisioning flag. command (str): Command used at beginning of provisioning. destroy_on_error (bool): vagrant destroy-on-error flag. vagrant_cwd (path): Location of `Vagrantfile`. Used if invoked with API only. vagrant_dotfile_path (path): Location of `.vagrant` metadata directory. Used if invoked with API only. vm_name (str): Name of the VM or container. """ # TODO: Add registering of VM for all of this installation to see if not ctx: # Using API. Else handled by cli. _set_init_vars(params.get("cwd"), params.get("tmpdir")) _set_vagrant_vars(params.get("vagrant_cwd"), params.get("vagrant_dotfile_path")) # Option Handling - These might modify the params dict and/or set env vars. params["guest_os"] = options.guest_os_option(params.get("guest_os")) params["box"] = options.box_option(params.get("box")) params["cpus"] = options.cpus_option(params.get("cpus")) params["hostname"] = options.hostname_option(params.get("hostname")) params["machine_type"] = options.machine_type_option( params.get("machine_type"), params.get("provider")) params["project_dir"] = options.project_dir_option( params.get("project_dir")) params["provider"] = options.provider_option(params.get("provider")) params["command"] = options.command_option(params.get("command")) params["ram_size"], params["drive_size"] = options.size_option( params.get("ram_size"), params.get("drive_size")) # both ram and drive size params["sync_dirs"] = options.sync_dirs_option(params.get("sync_dirs")) params["sync_type"] = options.sync_type_option(params.get("sync_type")) params["ports"] = options.ports_option(params.get("ports")) params["vm_name"] = options.vm_name_option(params.get("vm_name")) cmd = "up" # Provider specific handling. # Must come after all else, because logic may be done on params above. if params["provider"] == "digitalocean": vagrant_providers.digitalocean() elif params["provider"] == "docker": vagrant_providers.docker() elif params["provider"] == "ec2": vagrant_providers.ec2(**params) else: cmd += " --provider={}".format(params["provider"]) # Add straight pass-through flags. Keep test for True/False explicit as only those values should work if params.get("provision") is True: cmd = "{} {}".format(cmd, "--provision") elif params.get("provision") is False: cmd = "{} {}".format(cmd, "--no-provision") if params.get("destroy_on_error") is True: cmd = "{} {}".format(cmd, "--destroy-on-error") elif params.get("destroy_on_error") is False: cmd = "{} {}".format(cmd, "--no-destroy-on-error") exit_code = vagrant_general_command(cmd) if exit_code: with open(Path(get_env_var("LOG_PATH")) / "stderr.log") as fp: stderr = fp.readlines() for idx, line in enumerate(reversed(stderr)): if "Unknown configuration section 'disksize'" in line: abort("You probably don't have plugins installed.\nRun:\n" "\trambo install-plugins") elif idx > 5: # Only look through the recent stderr. break