Initialize a new container.

    This is a partial idempotent function as if it is already provisioned, we
    will reset a bit the lxc configuration file but much of the hard work will
    be escaped as markers will prevent re-execution of harmful tasks.

    name
        Name of the container

    image
        A tar archive to use as the rootfs for the container. Conflicts with
        the ``template`` argument.

    cpus
        Select a random number of cpu cores and assign it to the cpuset, if the
        cpuset option is set then this option will be ignored

    cpuset
        Explicitly define the cpus this container will be bound to

    cpushare
        cgroups cpu shares

    autostart
        autostart container on reboot

    memory
        cgroups memory limit, in MB

        .. versionchanged:: 2015.5.0
            If no value is passed, no limit is set. In earlier Salt versions,
            not passing this value causes a 1024MB memory limit to be set, and
            it was necessary to pass ``memory=0`` to set no limit.

    gateway
        the ipv4 gateway to use
        the default does nothing more than lxcutils does

    bridge
        the bridge to use
        the default does nothing more than lxcutils does

    network_profile
        Network profile to use for the container

        .. versionadded:: 2015.5.0

    nic_opts
        Extra options for network interfaces, will override

        ``{"eth0": {"hwaddr": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1", "ipv6": "2001:db8::ff00:42:8329"}}``

        or

        ``{"eth0": {"hwaddr": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1/24", "ipv6": "2001:db8::ff00:42:8329"}}``

    users
        Users for which the password defined in the ``password`` param should
        be set. Can be passed as a comma separated list or a python list.
        Defaults to just the ``root`` user.

    password
        Set the initial password for the users defined in the ``users``
        parameter

    password_encrypted : False
        Set to ``True`` to denote a password hash instead of a plaintext
        password

        .. versionadded:: 2015.5.0

    profile
        A LXC profile (defined in config or pillar).
        This can be either a real profile mapping or a string
        to retrieve it in configuration

    start
        Start the newly-created container

    dnsservers
        list of dns servers to set in the container, default [] (no setting)

    seed
        Seed the container with the minion config. Default: ``True``

    install
        If salt-minion is not already installed, install it. Default: ``True``

    config
        Optional config parameters. By default, the id is set to
        the name of the container.

    master
        salt master (default to minion's master)

    master_port
        salt master port (default to minion's master port)

    pub_key
        Explicit public key to preseed the minion with (optional).
        This can be either a filepath or a string representing the key

    priv_key
        Explicit private key to preseed the minion with (optional).
        This can be either a filepath or a string representing the key

    approve_key
        If explicit preseeding is not used;
        Attempt to request key approval from the master. Default: ``True``

    path
        path to the container parent directory
        default: /var/lib/lxc (system)

        .. versionadded:: 2015.8.0

    clone_from
        Original from which to use a clone operation to create the container.
        Default: ``None``

    bootstrap_delay
        Delay in seconds between end of container creation and bootstrapping.
        Useful when waiting for container to obtain a DHCP lease.

        .. versionadded:: 2015.5.0

    bootstrap_url
        See lxc.bootstrap

    bootstrap_shell
        See lxc.bootstrap

    bootstrap_args
        See lxc.bootstrap

    force_install
        Force installation even if salt-minion is detected,
        this is the way to run vendor bootstrap scripts even
        if a salt minion is already present in the container

    unconditional_install
        Run the script even if the container seems seeded

    CLI Example:

    .. code-block:: bash

        salt 'minion' lxc.init name [cpuset=cgroups_cpuset] \\
                [cpushare=cgroups_cpushare] [memory=cgroups_memory] \\
                [nic=nic_profile] [profile=lxc_profile] \\
                [nic_opts=nic_opts] [start=(True|False)] \\
                [seed=(True|False)] [install=(True|False)] \\
                [config=minion_config] [approve_key=(True|False) \\
                [clone_from=original] [autostart=True] \\
                [priv_key=/path_or_content] [pub_key=/path_or_content] \\
                [bridge=lxcbr0] [gateway=10.0.3.1] \\
                [dnsservers[dns1,dns2]] \\
                [users=[foo]] [password='secret'] \\
                [password_encrypted=(True|False)]

def init(name,
         config=None,
         cpuset=None,
         cpushare=None,
         memory=None,
         profile=None,
         network_profile=None,
         nic_opts=None,
         cpu=None,
         autostart=True,
         password=None,
         password_encrypted=None,
         users=None,
         dnsservers=None,
         searchdomains=None,
         bridge=None,
         gateway=None,
         pub_key=None,
         priv_key=None,
         force_install=False,
         unconditional_install=False,
         bootstrap_delay=None,
         bootstrap_args=None,
         bootstrap_shell=None,
         bootstrap_url=None,
         **kwargs):
    '''
    Initialize a new container.

    This is a partial idempotent function as if it is already provisioned, we
    will reset a bit the lxc configuration file but much of the hard work will
    be escaped as markers will prevent re-execution of harmful tasks.

    name
        Name of the container

    image
        A tar archive to use as the rootfs for the container. Conflicts with
        the ``template`` argument.

    cpus
        Select a random number of cpu cores and assign it to the cpuset, if the
        cpuset option is set then this option will be ignored

    cpuset
        Explicitly define the cpus this container will be bound to

    cpushare
        cgroups cpu shares

    autostart
        autostart container on reboot

    memory
        cgroups memory limit, in MB

        .. versionchanged:: 2015.5.0
            If no value is passed, no limit is set. In earlier Salt versions,
            not passing this value causes a 1024MB memory limit to be set, and
            it was necessary to pass ``memory=0`` to set no limit.

    gateway
        the ipv4 gateway to use
        the default does nothing more than lxcutils does

    bridge
        the bridge to use
        the default does nothing more than lxcutils does

    network_profile
        Network profile to use for the container

        .. versionadded:: 2015.5.0

    nic_opts
        Extra options for network interfaces, will override

        ``{"eth0": {"hwaddr": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1", "ipv6": "2001:db8::ff00:42:8329"}}``

        or

        ``{"eth0": {"hwaddr": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1/24", "ipv6": "2001:db8::ff00:42:8329"}}``

    users
        Users for which the password defined in the ``password`` param should
        be set. Can be passed as a comma separated list or a python list.
        Defaults to just the ``root`` user.

    password
        Set the initial password for the users defined in the ``users``
        parameter

    password_encrypted : False
        Set to ``True`` to denote a password hash instead of a plaintext
        password

        .. versionadded:: 2015.5.0

    profile
        A LXC profile (defined in config or pillar).
        This can be either a real profile mapping or a string
        to retrieve it in configuration

    start
        Start the newly-created container

    dnsservers
        list of dns servers to set in the container, default [] (no setting)

    seed
        Seed the container with the minion config. Default: ``True``

    install
        If salt-minion is not already installed, install it. Default: ``True``

    config
        Optional config parameters. By default, the id is set to
        the name of the container.

    master
        salt master (default to minion's master)

    master_port
        salt master port (default to minion's master port)

    pub_key
        Explicit public key to preseed the minion with (optional).
        This can be either a filepath or a string representing the key

    priv_key
        Explicit private key to preseed the minion with (optional).
        This can be either a filepath or a string representing the key

    approve_key
        If explicit preseeding is not used;
        Attempt to request key approval from the master. Default: ``True``

    path
        path to the container parent directory
        default: /var/lib/lxc (system)

        .. versionadded:: 2015.8.0

    clone_from
        Original from which to use a clone operation to create the container.
        Default: ``None``

    bootstrap_delay
        Delay in seconds between end of container creation and bootstrapping.
        Useful when waiting for container to obtain a DHCP lease.

        .. versionadded:: 2015.5.0

    bootstrap_url
        See lxc.bootstrap

    bootstrap_shell
        See lxc.bootstrap

    bootstrap_args
        See lxc.bootstrap

    force_install
        Force installation even if salt-minion is detected,
        this is the way to run vendor bootstrap scripts even
        if a salt minion is already present in the container

    unconditional_install
        Run the script even if the container seems seeded

    CLI Example:

    .. code-block:: bash

        salt 'minion' lxc.init name [cpuset=cgroups_cpuset] \\
                [cpushare=cgroups_cpushare] [memory=cgroups_memory] \\
                [nic=nic_profile] [profile=lxc_profile] \\
                [nic_opts=nic_opts] [start=(True|False)] \\
                [seed=(True|False)] [install=(True|False)] \\
                [config=minion_config] [approve_key=(True|False) \\
                [clone_from=original] [autostart=True] \\
                [priv_key=/path_or_content] [pub_key=/path_or_content] \\
                [bridge=lxcbr0] [gateway=10.0.3.1] \\
                [dnsservers[dns1,dns2]] \\
                [users=[foo]] [password='secret'] \\
                [password_encrypted=(True|False)]

    '''
    ret = {'name': name,
           'changes': {}}

    profile = get_container_profile(copy.deepcopy(profile))
    if not network_profile:
        network_profile = profile.get('network_profile')
    if not network_profile:
        network_profile = DEFAULT_NIC

    # Changes is a pointer to changes_dict['init']. This method is used so that
    # we can have a list of changes as they are made, providing an ordered list
    # of things that were changed.
    changes_dict = {'init': []}
    changes = changes_dict.get('init')

    if users is None:
        users = []
    dusers = ['root']
    for user in dusers:
        if user not in users:
            users.append(user)

    kw_overrides = copy.deepcopy(kwargs)

    def select(key, default=None):
        kw_overrides_match = kw_overrides.pop(key, _marker)
        profile_match = profile.pop(key, default)
        # let kwarg overrides be the preferred choice
        if kw_overrides_match is _marker:
            return profile_match
        return kw_overrides_match

    path = select('path')
    bpath = get_root_path(path)
    state_pre = state(name, path=path)
    tvg = select('vgname')
    vgname = tvg if tvg else __salt__['config.get']('lxc.vgname')
    start_ = select('start', True)
    autostart = select('autostart', autostart)
    seed = select('seed', True)
    install = select('install', True)
    seed_cmd = select('seed_cmd')
    salt_config = _get_salt_config(config, **kwargs)
    approve_key = select('approve_key', True)
    clone_from = select('clone_from')

    # If using a volume group then set up to make snapshot cow clones
    if vgname and not clone_from:
        try:
            kwargs['vgname'] = vgname
            clone_from = _get_base(profile=profile, **kwargs)
        except (SaltInvocationError, CommandExecutionError) as exc:
            ret['comment'] = exc.strerror
            if changes:
                ret['changes'] = changes_dict
            return ret
        if not kwargs.get('snapshot') is False:
            kwargs['snapshot'] = True
    does_exist = exists(name, path=path)
    to_reboot = False
    remove_seed_marker = False
    if does_exist:
        pass
    elif clone_from:
        remove_seed_marker = True
        try:
            clone(name, clone_from, profile=profile, **kwargs)
            changes.append({'create': 'Container cloned'})
        except (SaltInvocationError, CommandExecutionError) as exc:
            if 'already exists' in exc.strerror:
                changes.append({'create': 'Container already exists'})
            else:
                ret['result'] = False
                ret['comment'] = exc.strerror
                if changes:
                    ret['changes'] = changes_dict
                return ret
        cfg = _LXCConfig(name=name, network_profile=network_profile,
                         nic_opts=nic_opts, bridge=bridge, path=path,
                         gateway=gateway, autostart=autostart,
                         cpuset=cpuset, cpushare=cpushare, memory=memory)
        old_chunks = read_conf(cfg.path, out_format='commented')
        cfg.write()
        chunks = read_conf(cfg.path, out_format='commented')
        if old_chunks != chunks:
            to_reboot = True
    else:
        remove_seed_marker = True
        cfg = _LXCConfig(network_profile=network_profile,
                         nic_opts=nic_opts, cpuset=cpuset, path=path,
                         bridge=bridge, gateway=gateway,
                         autostart=autostart,
                         cpushare=cpushare, memory=memory)
        with cfg.tempfile() as cfile:
            try:
                create(name, config=cfile.name, profile=profile, **kwargs)
                changes.append({'create': 'Container created'})
            except (SaltInvocationError, CommandExecutionError) as exc:
                if 'already exists' in exc.strerror:
                    changes.append({'create': 'Container already exists'})
                else:
                    ret['comment'] = exc.strerror
                    if changes:
                        ret['changes'] = changes_dict
                    return ret
        cpath = os.path.join(bpath, name, 'config')
        old_chunks = []
        if os.path.exists(cpath):
            old_chunks = read_conf(cpath, out_format='commented')
        new_cfg = _config_list(conf_tuples=old_chunks,
                               cpu=cpu,
                               network_profile=network_profile,
                               nic_opts=nic_opts, bridge=bridge,
                               cpuset=cpuset, cpushare=cpushare,
                               memory=memory)
        if new_cfg:
            edit_conf(cpath, out_format='commented', lxc_config=new_cfg)
        chunks = read_conf(cpath, out_format='commented')
        if old_chunks != chunks:
            to_reboot = True

    # last time to be sure any of our property is correctly applied
    cfg = _LXCConfig(name=name, network_profile=network_profile,
                     nic_opts=nic_opts, bridge=bridge, path=path,
                     gateway=gateway, autostart=autostart,
                     cpuset=cpuset, cpushare=cpushare, memory=memory)
    old_chunks = []
    if os.path.exists(cfg.path):
        old_chunks = read_conf(cfg.path, out_format='commented')
    cfg.write()
    chunks = read_conf(cfg.path, out_format='commented')
    if old_chunks != chunks:
        changes.append({'config': 'Container configuration updated'})
        to_reboot = True

    if to_reboot:
        try:
            stop(name, path=path)
        except (SaltInvocationError, CommandExecutionError) as exc:
            ret['comment'] = 'Unable to stop container: {0}'.format(exc)
            if changes:
                ret['changes'] = changes_dict
            return ret
    if not does_exist or (does_exist and state(name, path=path) != 'running'):
        try:
            start(name, path=path)
        except (SaltInvocationError, CommandExecutionError) as exc:
            ret['comment'] = 'Unable to stop container: {0}'.format(exc)
            if changes:
                ret['changes'] = changes_dict
            return ret

    if remove_seed_marker:
        run(name,
            'rm -f \'{0}\''.format(SEED_MARKER),
            path=path,
            chroot_fallback=False,
            python_shell=False)

    # set the default user/password, only the first time
    if ret.get('result', True) and password:
        gid = '/.lxc.initial_pass'
        gids = [gid,
                '/lxc.initial_pass',
                '/.lxc.{0}.initial_pass'.format(name)]
        if not any(retcode(name,
                           'test -e "{0}"'.format(x),
                           chroot_fallback=True,
                           path=path,
                           ignore_retcode=True) == 0
                   for x in gids):
            # think to touch the default user generated by default templates
            # which has a really unsecure passwords...
            # root is defined as a member earlier in the code
            for default_user in ['ubuntu']:
                if (
                    default_user not in users and
                    retcode(name,
                            'id {0}'.format(default_user),
                            python_shell=False,
                            path=path,
                            chroot_fallback=True,
                            ignore_retcode=True) == 0
                ):
                    users.append(default_user)
            for user in users:
                try:
                    cret = set_password(name,
                                        users=[user],
                                        path=path,
                                        password=password,
                                        encrypted=password_encrypted)
                except (SaltInvocationError, CommandExecutionError) as exc:
                    msg = '{0}: Failed to set password'.format(
                        user) + exc.strerror
                    # only hardfail in unrecoverable situation:
                    # root cannot be setted up
                    if user == 'root':
                        ret['comment'] = msg
                        ret['result'] = False
                    else:
                        log.debug(msg)
            if ret.get('result', True):
                changes.append({'password': 'Password(s) updated'})
                if retcode(name,
                           ('sh -c \'touch "{0}"; test -e "{0}"\''
                            .format(gid)),
                           path=path,
                           chroot_fallback=True,
                           ignore_retcode=True) != 0:
                    ret['comment'] = 'Failed to set password marker'
                    changes[-1]['password'] += '. ' + ret['comment'] + '.'
                    ret['result'] = False

    # set dns servers if any, only the first time
    if ret.get('result', True) and dnsservers:
        # retro compatibility, test also old markers
        gid = '/.lxc.initial_dns'
        gids = [gid,
                '/lxc.initial_dns',
                '/lxc.{0}.initial_dns'.format(name)]
        if not any(retcode(name,
                           'test -e "{0}"'.format(x),
                           chroot_fallback=True,
                           path=path,
                           ignore_retcode=True) == 0
                   for x in gids):
            try:
                set_dns(name,
                        path=path,
                        dnsservers=dnsservers,
                        searchdomains=searchdomains)
            except (SaltInvocationError, CommandExecutionError) as exc:
                ret['comment'] = 'Failed to set DNS: ' + exc.strerror
                ret['result'] = False
            else:
                changes.append({'dns': 'DNS updated'})
                if retcode(name,
                           ('sh -c \'touch "{0}"; test -e "{0}"\''
                            .format(gid)),
                           chroot_fallback=True,
                           path=path,
                           ignore_retcode=True) != 0:
                    ret['comment'] = 'Failed to set DNS marker'
                    changes[-1]['dns'] += '. ' + ret['comment'] + '.'
                    ret['result'] = False

    # retro compatibility, test also old markers
    if remove_seed_marker:
        run(name,
            'rm -f \'{0}\''.format(SEED_MARKER),
            path=path,
            python_shell=False)
    gid = '/.lxc.initial_seed'
    gids = [gid, '/lxc.initial_seed']
    if (
        any(retcode(name,
                    'test -e {0}'.format(x),
                    path=path,
                    chroot_fallback=True,
                    ignore_retcode=True) == 0
            for x in gids) or not ret.get('result', True)
    ):
        pass
    elif seed or seed_cmd:
        if seed:
            try:
                result = bootstrap(
                    name, config=salt_config, path=path,
                    approve_key=approve_key,
                    pub_key=pub_key, priv_key=priv_key,
                    install=install,
                    force_install=force_install,
                    unconditional_install=unconditional_install,
                    bootstrap_delay=bootstrap_delay,
                    bootstrap_url=bootstrap_url,
                    bootstrap_shell=bootstrap_shell,
                    bootstrap_args=bootstrap_args)
            except (SaltInvocationError, CommandExecutionError) as exc:
                ret['comment'] = 'Bootstrap failed: ' + exc.strerror
                ret['result'] = False
            else:
                if not result:
                    ret['comment'] = ('Bootstrap failed, see minion log for '
                                      'more information')
                    ret['result'] = False
                else:
                    changes.append(
                        {'bootstrap': 'Container successfully bootstrapped'}
                    )
        elif seed_cmd:
            try:
                result = __salt__[seed_cmd](info(name, path=path)['rootfs'],
                                            name,
                                            salt_config)
            except (SaltInvocationError, CommandExecutionError) as exc:
                ret['comment'] = ('Bootstrap via seed_cmd \'{0}\' failed: {1}'
                                  .format(seed_cmd, exc.strerror))
                ret['result'] = False
            else:
                if not result:
                    ret['comment'] = ('Bootstrap via seed_cmd \'{0}\' failed, '
                                      'see minion log for more information '
                                      .format(seed_cmd))
                    ret['result'] = False
                else:
                    changes.append(
                        {'bootstrap': 'Container successfully bootstrapped '
                                      'using seed_cmd \'{0}\''
                                      .format(seed_cmd)}
                    )

    if ret.get('result', True) and not start_:
        try:
            stop(name, path=path)
        except (SaltInvocationError, CommandExecutionError) as exc:
            ret['comment'] = 'Unable to stop container: {0}'.format(exc)
            ret['result'] = False

    state_post = state(name, path=path)
    if state_pre != state_post:
        changes.append({'state': {'old': state_pre, 'new': state_post}})

    if ret.get('result', True):
        ret['comment'] = ('Container \'{0}\' successfully initialized'
                          .format(name))
        ret['result'] = True
    if changes:
        ret['changes'] = changes_dict
    return ret