예제 #1
0
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)
예제 #2
0
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)))
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
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"])
예제 #8
0
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
예제 #9
0
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."
        )
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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