text: # -*- coding: utf-8 -*-
  '''
  Control Linux Containers via Salt
  
  :depends: lxc package for distribution
  
  lxc >= 1.0 (even beta alpha) is required
  
  '''
  
  from __future__ import absolute_import
  
  # Import python libs
  from __future__ import print_function
  import traceback
  import datetime
  import pipes
  import copy
  import logging
  import tempfile
  import os
  import time
  import shutil
  import re
  import random
  import salt.ext.six as six
  from salt.ext.six.moves.urllib.parse import urlparse as _urlparse  # pylint: disable=E0611
  
  # Import salt libs
  import salt
  import salt.utils.odict
  import salt.utils
  import salt.utils.dictupdate
  from salt.utils import vt
  import salt.utils.cloud
  import salt.config
  
  # Set up logging
  log = logging.getLogger(__name__)
  
  # Don't shadow built-in's.
  __func_alias__ = {
      'list_': 'list',
      'ls_': 'ls'
  }
  
  
  DEFAULT_NIC_PROFILE = {'eth0': {'link': 'br0', 'type': 'veth'}}
  SEED_MARKER = '/lxc.initial_seed'
  _marker = object()
  
  
  def _ip_sort(ip):
      '''Ip sorting'''
      idx = '001'
      if ip == '127.0.0.1':
          idx = '200'
      if ip == '::1':
          idx = '201'
      elif '::' in ip:
          idx = '100'
      return '{0}___{1}'.format(idx, ip)
  
  
  def cloud_init_interface(name, vm_=None, **kwargs):
      '''
      Interface between salt.cloud.lxc driver and lxc.init
      ``vm_`` is a mapping of vm opts in the salt.cloud format
      as documented for the lxc driver.
  
      This can be used either:
  
      - from the salt cloud driver
      - because you find the argument to give easier here
        than using directly lxc.init
  
      .. warning::
          BE REALLY CAREFUL CHANGING DEFAULTS !!!
          IT'S A RETRO COMPATIBLE INTERFACE WITH
          THE SALT CLOUD DRIVER (ask kiorky).
  
      name
          name of the lxc container to create
      from_container
          which container we use as a template
          when running lxc.clone
      image
          which template do we use when we
          are using lxc.create. This is the default
          mode unless you specify something in from_container
      backing
          which backing store to use.
          Values can be: overlayfs, dir(default), lvm, zfs, brtfs
      fstype
          When using a blockdevice level backing store,
          which filesystem to use on
      size
          When using a blockdevice level backing store,
          which size for the filesystem to use on
      snapshot
          Use snapshot when cloning the container source
      vgname
          if using LVM: vgname
      lgname
          if using LVM: lvname
      pub_key
          public key to preseed the minion with.
          Can be the keycontent or a filepath
      priv_key
          private key to preseed the minion with.
          Can be the keycontent or a filepath
      ip
          ip for the primary nic
      mac
          mac for the primary nic
      netmask
          netmask for the primary nic (24)
          = ``vm_.get('netmask', '24')``
      bridge
          bridge for the primary nic (lxcbr0)
      gateway
          network gateway for the container
      additional_ips
          additional ips which will be wired on the main bridge (br0)
          which is connected to internet.
          Be aware that you may use manual virtual mac addresses
          provided by you provider (online, ovh, etc).
          This is a list of mappings ``{ip: '', mac: '',netmask:''}``
          Set gateway to ``None`` and an interface with a gateway
          to escape from another interface that's eth0.
          e.g.:
  
          .. code-block:: python
  
              {'mac': '00:16:3e:01:29:40',
               'gateway': None, #default
               'link': 'br0', #default
               'netmask': '', #default
               'ip': '22.1.4.25'}
  
      unconditional_install
          given to lxc.bootstrap (see relative doc)
      force_install
          given to lxc.bootstrap (see relative doc)
      config
          any extra argument for the salt minion config
      dnsservers
          dns servers to set inside the container
      autostart
          autostart the container at boot time
      password
          administrative password for the container
      users
          administrative users for the container
          default: [root] and [root, ubuntu] on ubuntu
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.cloud_init_interface foo
  
      '''
      if vm_ is None:
          vm_ = {}
      vm_ = copy.deepcopy(vm_)
      vm_ = salt.utils.dictupdate.update(vm_, kwargs)
      profile = _lxc_profile(vm_.get('profile', {}))
      if name is None:
          name = vm_['name']
      from_container = vm_.get('from_container', None)
      # if we are on ubuntu, default to ubuntu
      default_template = ''
      if __grains__.get('os', '') in ['Ubuntu']:
          default_template = 'ubuntu'
      image = vm_.get('image', profile.get('template',
                                           default_template))
      vgname = vm_.get('vgname', None)
      backing = vm_.get('backing', 'dir')
      snapshot = vm_.get('snapshot', False)
      autostart = bool(vm_.get('autostart', True))
      dnsservers = vm_.get('dnsservers', [])
      if not dnsservers:
          dnsservers = ['8.8.8.8', '4.4.4.4']
      password = vm_.get('password', 's3cr3t')
      fstype = vm_.get('fstype', None)
      lvname = vm_.get('lvname', None)
      pub_key = vm_.get('pub_key', None)
      priv_key = vm_.get('priv_key', None)
      size = vm_.get('size', '20G')
      script = vm_.get('script', None)
      script_args = vm_.get('script_args', None)
      if image:
          profile['template'] = image
      if vgname:
          profile['vgname'] = vgname
      if backing:
          profile['backing'] = backing
      users = vm_.get('users', None)
      if users is None:
          users = []
      ssh_username = vm_.get('ssh_username', None)
      if ssh_username and (ssh_username not in users):
          users.append(ssh_username)
      ip = vm_.get('ip', None)
      mac = vm_.get('mac', None)
      netmask = vm_.get('netmask', '24')
      bridge = vm_.get('bridge', 'lxcbr0')
      gateway = vm_.get('gateway', 'auto')
      unconditional_install = vm_.get('unconditional_install', False)
      force_install = vm_.get('force_install', True)
      config = vm_.get('config', {})
      if not config:
          config = vm_.get('minion', {})
      if not config:
          config = {}
      config.setdefault('master',
                        vm_.get('master',
                                __opts__.get('master',
                                             __opts__['id'])))
      config.setdefault(
          'master_port',
          vm_.get('master_port',
                  __opts__.get('master_port',
                               __opts__.get('ret_port',
                                            __opts__.get('4506')))))
      if not config['master']:
          config = {}
      eth0 = {}
      nic_opts = {'eth0': eth0}
      bridge = vm_.get('bridge', 'lxcbr0')
      if ip is None:
          nic_opts = None
      else:
          fullip = ip
          if netmask:
              fullip += '/{0}'.format(netmask)
          eth0['ipv4'] = fullip
          if mac is not None:
              eth0['hwaddr'] = mac
          if bridge:
              eth0['link'] = bridge
      for ix, iopts in enumerate(vm_.get("additional_ips", [])):
          ifh = "eth{0}".format(ix+1)
          ethx = nic_opts.setdefault(ifh, {})
          gw = iopts.get('gateway', None)
          if gw:
              ethx['gateway'] = gw
              vm_['gateway'] = gateway = None
          ethx['link'] = iopts.get('link', 'br0')
          ethx['ipv4'] = iopts['ip']
          nm = iopts.get('netmask', '')
          if nm:
              ethx['ipv4'] += '/{0}'.format(nm)
          for i in ['mac', 'hwaddr']:
              if i in iopts:
                  ethx['hwaddr'] = iopts[i]
          if 'hwaddr' not in ethx:
              raise ValueError('No mac for {0}'.format(ifh))
      gateway = vm_.get('gateway', 'auto')
      #
      lxc_init_interface = {}
      lxc_init_interface['name'] = name
      lxc_init_interface['config'] = config
      lxc_init_interface['memory'] = 0  # nolimit
      lxc_init_interface['pub_key'] = pub_key
      lxc_init_interface['priv_key'] = priv_key
      lxc_init_interface['bridge'] = bridge
      lxc_init_interface['gateway'] = gateway
      lxc_init_interface['nic_opts'] = nic_opts
      lxc_init_interface['clone'] = from_container
      lxc_init_interface['profile'] = profile
      lxc_init_interface['snapshot'] = snapshot
      lxc_init_interface['dnsservers'] = dnsservers
      lxc_init_interface['fstype'] = fstype
      lxc_init_interface['vgname'] = vgname
      lxc_init_interface['size'] = size
      lxc_init_interface['lvname'] = lvname
      lxc_init_interface['force_install'] = force_install
      lxc_init_interface['unconditional_install'] = (
          unconditional_install
      )
      lxc_init_interface['bootstrap_url'] = script
      lxc_init_interface['bootstrap_args'] = script_args
      lxc_init_interface['bootstrap_shell'] = 'sh'
      lxc_init_interface['autostart'] = autostart
      lxc_init_interface['users'] = users
      lxc_init_interface['password'] = password
      return lxc_init_interface
  
  
  def __virtual__():
      if salt.utils.which('lxc-start'):
          return True
      # To speed up the whole thing, we decided to not use the
      # subshell way and assume things are in place for lxc
      # Discussion made by @kiorky and @thatch45
  
      # lxc-version presence is not sufficient, in lxc1.0 alpha
      # (precise backports), we have it and it is sufficient
      # for the module to execute.
      # elif salt.utils.which('lxc-version'):
      #     passed = False
      #     try:
      #         passed = subprocess.check_output(
      #             'lxc-version').split(':')[1].strip() >= '1.0'
      #     except Exception:
      #         pass
      #     if not passed:
      #         log.warning('Support for lxc < 1.0 may be incomplete.')
      #     return 'lxc'
      # return False
      #
      return False
  
  
  def _lxc_profile(profile):
      '''
      Gather the lxc profile from the config or apply the default (empty).
  
      Profiles can be defined in the config or pillar, e.g.:
  
      Profile can be a string to be retrieved in config
      or a mapping.
  
      If is is a mapping and it contains a name, the name will
      be used to grab defaults in config as if the script was called
      with a string. This let you override either all opts or just specific ones.
  
      The resulting profile will be cached inside the context for further
      quick access
  
      .. code-block:: yaml
  
          lxc.profile:
            ubuntu:
              template: ubuntu
              backing: lvm
              vgname: lxc
              size: 1G
  
      ::
  
          salt-call lxc.profile ubuntu
          salt-call lxc.profile \\
                  {'name': 'ubuntu', 'template': 'myapp', \\
                  'backing': 'overlayfs'}
  
      '''
      profilename = profile
      if isinstance(profile, dict):
          profilename = profile.get('name', 'ubuntu')
      else:
          profile = {}
      key = 'lxc.profile.{0}'.format(profilename)
      rprofile = __context__.get(key, {})
      if not rprofile:
          default_profile = __salt__['config.get'](
              'lxc.profile', {}).get(profilename, {})
          # save the resulting profile in the context
          rprofile = salt.utils.dictupdate.update(
              copy.deepcopy(default_profile),
              copy.deepcopy(profile))
          __context__[key] = rprofile
      return rprofile
  
  
  def _rand_cpu_str(cpu):
      '''
      Return a random subset of cpus for the cpuset config
      '''
      cpu = int(cpu)
      avail = __salt__['status.nproc']()
      if cpu < avail:
          return '0-{0}'.format(avail)
      to_set = set()
      while len(to_set) < cpu:
          choice = random.randint(0, avail - 1)
          if choice not in to_set:
              to_set.add(str(choice))
      return ','.join(sorted(to_set))
  
  
  def _get_network_conf(conf_tuples=None, **kwargs):
      nic = kwargs.pop('nic', None)
      ret = []
      if not nic:
          return ret
      kwargs = copy.deepcopy(kwargs)
      gateway = kwargs.pop('gateway', None)
      bridge = kwargs.get('bridge', None)
      if not conf_tuples:
          conf_tuples = []
  
      if nic:
          nicp = __salt__['config.get']('lxc.nic', {}).get(
              nic, DEFAULT_NIC_PROFILE)
          nic_opts = kwargs.pop('nic_opts', {})
          if nic_opts:
              for dev, args in nic_opts.items():
                  ethx = nicp.setdefault(dev, {})
                  ethx = salt.utils.dictupdate.update(ethx, args)
          ifs = [a for a in nicp]
          ifs.sort()
          gateway_set = False
          for dev in ifs:
              args = nicp[dev]
              ret.append({'lxc.network.type': args.pop('type', '')})
              ret.append({'lxc.network.name': dev})
              ret.append({'lxc.network.flags': args.pop('flags', 'up')})
              opts = nic_opts.get(dev) if nic_opts else {}
              mac = opts.get('mac', '')
              if opts:
                  ipv4 = opts.get('ipv4')
                  ipv6 = opts.get('ipv6')
              else:
                  ipv4, ipv6 = None, None
                  if not mac:
                      mac = salt.utils.gen_mac()
              if mac:
                  ret.append({'lxc.network.hwaddr': mac})
              if ipv4:
                  ret.append({'lxc.network.ipv4': ipv4})
              if ipv6:
                  ret.append({'lxc.network.ipv6': ipv6})
              for k, v in args.items():
                  if k == 'link' and bridge:
                      v = bridge
                  v = opts.get(k, v)
                  if k in ['gateway', 'mac']:
                      continue
                  ret.append({'lxc.network.{0}'.format(k): v})
              # gateway (in automode) must be appended following network conf !
              if not gateway:
                  gateway = args.get('gateway', None)
              if gateway is not None and not gateway_set:
                  ret.append({'lxc.network.ipv4.gateway': gateway})
                  # only one network gateway ;)
                  gateway_set = True
          # normally, this wont happen
          # set the gateway if specified even if we did
          # not managed the network underlying
          if gateway is not None and not gateway_set:
              ret.append({'lxc.network.ipv4.gateway': gateway})
              # only one network gateway ;)
              gateway_set = True
  
      old = _get_veths(conf_tuples)
      new = _get_veths(ret)
      # verify that we did not loose the mac settings
      for iface in [a for a in new]:
          ndata = new[iface]
          nmac = ndata.get('lxc.network.hwaddr', '')
          ntype = ndata.get('lxc.network.type', '')
          omac, otype = '', ''
          if iface in old:
              odata = old[iface]
              omac = odata.get('lxc.network.hwaddr', '')
              otype = odata.get('lxc.network.type', '')
          # default for network type is setted here
          # attention not to change the network type
          # without a good and explicit reason to.
          if otype and not ntype:
              ntype = otype
          if not ntype:
              ntype = 'veth'
          new[iface]['lxc.network.type'] = ntype
          if omac and not nmac:
              new[iface]['lxc.network.hwaddr'] = omac
  
      ret = []
      for v in new.values():
          for row in v:
              ret.append({row: v[row]})
      return ret
  
  
  def _get_memory(memory):
      '''
      Handle the saltcloud driver and lxc runner memory restriction
      differences.
      Runner limits to 1024MB by default
      SaltCloud does not restrict memory usage by default
      '''
      if memory is None:
          memory = 1024
      if memory:
          memory = memory * 1024 * 1024
      return memory
  
  
  def _get_autostart(autostart):
      if autostart is None:
          autostart = True
      if autostart:
          autostart = '1'
      else:
          autostart = '0'
      return autostart
  
  
  def _get_lxc_default_data(**kwargs):
      kwargs = copy.deepcopy(kwargs)
      ret = {}
      autostart = _get_autostart(kwargs.pop('autostart', None))
      ret['lxc.start.auto'] = autostart
      memory = _get_memory(kwargs.pop('memory', None))
      if memory:
          ret['lxc.cgroup.memory.limit_in_bytes'] = memory
      cpuset = kwargs.pop('cpuset', None)
      if cpuset:
          ret['lxc.cgroup.cpuset.cpus'] = cpuset
      cpushare = kwargs.pop('cpushare', None)
      cpu = kwargs.pop('cpu', None)
      if cpushare:
          ret['lxc.cgroup.cpu.shares'] = cpushare
      if cpu and not cpuset:
          ret['lxc.cgroup.cpuset.cpus'] = _rand_cpu_str(cpu)
      return ret
  
  
  def _config_list(conf_tuples=None, **kwargs):
      '''
      Return a list of dicts from the salt level configurations
      '''
      if not conf_tuples:
          conf_tuples = []
      kwargs = copy.deepcopy(kwargs)
      ret = []
      default_data = _get_lxc_default_data(**kwargs)
      for k, val in default_data.items():
          ret.append({k: val})
      net_datas = _get_network_conf(conf_tuples=conf_tuples, **kwargs)
      ret.extend(net_datas)
      return ret
  
  
  def _get_veths(net_data):
      '''Parse the nic setup inside lxc conf tuples back
      to a dictionary indexed by network interface'''
      if isinstance(net_data, dict):
          net_data = list(net_data.items())
      nics = salt.utils.odict.OrderedDict()
      current_nic = salt.utils.odict.OrderedDict()
      for item in net_data:
          if item and isinstance(item, dict):
              item = list(item.items())[0]
          if item[0] == 'lxc.network.type':
              current_nic = salt.utils.odict.OrderedDict()
          if item[0] == 'lxc.network.name':
              nics[item[1].strip()] = current_nic
          current_nic[item[0].strip()] = item[1].strip()
      return nics
  
  
  class _LXCConfig(object):
      '''
      LXC configuration data
      '''
      pattern = re.compile(r'^(\S+)(\s*)(=)(\s*)(.*)')
      non_interpretable_pattern = re.compile(r'^((#.*)|(\s*))$')
  
      def __init__(self, **kwargs):
          kwargs = copy.deepcopy(kwargs)
          self.name = kwargs.pop('name', None)
          self.data = []
          if self.name:
              self.path = '/var/lib/lxc/{0}/config'.format(self.name)
              if os.path.isfile(self.path):
                  with salt.utils.fopen(self.path) as f:
                      for l in f.readlines():
                          match = self.pattern.findall((l.strip()))
                          if match:
                              self.data.append((match[0][0], match[0][-1]))
                          match = self.non_interpretable_pattern.findall(
                              (l.strip()))
                          if match:
                              self.data.append(('', match[0][0]))
          else:
              self.path = None
  
          def _replace(k, v):
              if v:
                  self._filter_data(k)
                  self.data.append((k, v))
  
          default_data = _get_lxc_default_data(**kwargs)
          for k, val in default_data.items():
              _replace(k, val)
          old_net = self._filter_data('lxc.network')
          net_datas = _get_network_conf(conf_tuples=old_net, **kwargs)
          if net_datas:
              for row in net_datas:
                  self.data.extend(list(row.items()))
  
          # be sure to reset harmful settings
          for i in ['lxc.cgroup.memory.limit_in_bytes']:
              if not default_data.get(i):
                  self._filter_data(i)
  
      def as_string(self):
          chunks = ('{0[0]}{1}{0[1]}'.format(item, (' = ' if item[0] else '')) for item in self.data)
          return '\n'.join(chunks) + '\n'
  
      def write(self):
          if self.path:
              content = self.as_string()
              # 2 step rendering to be sure not to open/wipe the config
              # before as_string succeeds.
              with salt.utils.fopen(self.path, 'w') as fic:
                  fic.write(content)
                  fic.flush()
  
      def tempfile(self):
          # this might look like the function name is shadowing the
          # module, but it's not since the method belongs to the class
          f = tempfile.NamedTemporaryFile()
          f.write(self.as_string())
          f.flush()
          return f
  
      def _filter_data(self, pat):
          removed = []
          x = []
          for i in self.data:
              if not re.match('^' + pat, i[0]):
                  x.append(i)
              else:
                  removed.append(i)
          self.data = x
          return removed
  
  
  def get_base(**kwargs):
      '''
      If the needed base does not exist, then create it, if it does exist
      create nothing and return the name of the base lxc container so
      it can be cloned.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.init name [cpuset=cgroups_cpuset] \\
                  [nic=nic_profile] [profile=lxc_profile] \\
                  [nic_opts=nic_opts] [image=network image path]\\
                  [seed=(True|False)] [install=(True|False)] \\
                  [config=minion_config]
      '''
      cntrs = ls_()
      if kwargs.get('image'):
          image = kwargs.get('image')
          proto = _urlparse(image).scheme
          img_tar = __salt__['cp.cache_file'](image)
          img_name = os.path.basename(img_tar)
          hash_ = salt.utils.get_hash(
                  img_tar,
                  __salt__['config.get']('hash_type'))
          name = '__base_{0}_{1}_{2}'.format(proto, img_name, hash_)
          if name not in cntrs:
              create(name, **kwargs)
              if kwargs.get('vgname'):
                  rootfs = os.path.join('/dev', kwargs['vgname'], name)
                  lxc_info = info(name)
                  edit_conf(lxc_info['config'], **{'lxc.rootfs': rootfs})
          return name
      elif kwargs.get('template'):
          name = '__base_{0}'.format(kwargs['template'])
          if name not in cntrs:
              create(name, **kwargs)
              if kwargs.get('vgname'):
                  rootfs = os.path.join('/dev', kwargs['vgname'], name)
                  lxc_info = info(name)
                  edit_conf(lxc_info['config'], **{'lxc.rootfs': rootfs})
          return name
      return ''
  
  
  def init(name,
           cpuset=None,
           cpushare=None,
           memory=None,
           nic='default',
           profile=None,
           nic_opts=None,
           cpu=None,
           autostart=True,
           password=None,
           users=None,
           dnsservers=None,
           bridge=None,
           gateway=None,
           pub_key=None,
           priv_key=None,
           force_install=False,
           unconditional_install=False,
           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.
      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.
          (0 for nolimit, None for old default 1024MB)
      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
      nic
          Network interfaces profile (defined in config or pillar).
      users
          Sysadmins users to set the administrative password to
          e.g. [root, ubuntu, sysadmin], default [root] and [root, ubuntu]
          on ubuntu
      password
          Set the initial password for default sysadmin users, at least root
          but also can be used for sudoers, e.g. [root, ubuntu, sysadmin]
      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
      nic_opts
          Extra options for network interfaces. E.g.:
  
          .. code-block:: bash
  
              {"eth0": {"mac": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1", "ipv6": "2001:db8::ff00:42:8329"}}
  
          or
  
          .. code-block:: bash
  
              {"eth0": {"mac": "aa:bb:cc:dd:ee:ff", "ipv4": "10.1.1.1/24", "ipv6": "2001:db8::ff00:42:8329"}}
      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.
      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``
      clone
          Original from which to use a clone operation to create the container.
          Default: ``None``
      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=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'
  
      '''
      kwargs = copy.deepcopy(kwargs)
      comment = ''
      ret = {'error': '', 'name': name, 'result': True}
      changes = ret.setdefault('changes', {})
      if users is None:
          users = []
      dusers = ['root']
      if (
          __grains__['os'] in ['Ubuntu']
          and 'ubuntu' not in users
      ):
          dusers.append('ubuntu')
      for user in dusers:
          if user not in users:
              users.append(user)
      if not isinstance(profile, dict):
          profile = _lxc_profile(profile)
      profile = copy.deepcopy(profile)
  
      def select(k, default=None):
          kw = kwargs.pop(k, _marker)
          p = profile.pop(k, default)
          # let kwargs be really be the preferred choice
          if kw is _marker:
              kw = p
          return kw
  
      tvg = select('vgname')
      vgname = tvg if tvg else __salt__['config.get']('lxc.vgname')
      start_ = select('start', True)
      ret['started'] = start_
      autostart = select('autostart', autostart)
      seed = select('seed', True)
      install = select('install', True)
      seed_cmd = select('seed_cmd')
      salt_config = select('config')
      approve_key = select('approve_key', True)
      clone_from = select('clone')
  
      # If using a volume group then set up to make snapshot cow clones
      if vgname and not clone_from:
          clone_from = get_base(vgname=vgname, **kwargs)
          if not kwargs.get('snapshot') is False:
              kwargs['snapshot'] = True
      does_exist = exists(name)
      to_reboot = False
      remove_seed_marker = False
      if does_exist:
          comment += 'Container already exists\n'
      elif clone_from:
          remove_seed_marker = True
          ret.update(
              clone(name, clone_from,
                                    profile=profile, **kwargs))
          if not ret.get('cloned', False):
              return ret
          cfg = _LXCConfig(name=name, nic=nic, nic_opts=nic_opts,
                           bridge=bridge, gateway=gateway,
                           autostart=autostart,
                           cpuset=cpuset, cpushare=cpushare, memory=memory)
          old_chunks = read_conf(cfg.path)
          cfg.write()
          chunks = read_conf(cfg.path)
          if old_chunks != chunks:
              to_reboot = True
      else:
          remove_seed_marker = True
          cfg = _LXCConfig(nic=nic, nic_opts=nic_opts, cpuset=cpuset,
                           bridge=bridge, gateway=gateway,
                           autostart=autostart,
                           cpushare=cpushare, memory=memory)
          with cfg.tempfile() as cfile:
              ret.update(
                  create(name, config=cfile.name,
                                         profile=profile, **kwargs))
          if not ret.get('created', False):
              return ret
          path = '/var/lib/lxc/{0}/config'.format(name)
          old_chunks = []
          if os.path.exists(path):
              old_chunks = read_conf(path)
          for comp in _config_list(conf_tuples=old_chunks,
                                   cpu=cpu,
                                   nic=nic, nic_opts=nic_opts, bridge=bridge,
                                   cpuset=cpuset, cpushare=cpushare,
                                   memory=memory):
              edit_conf(path, **comp)
          chunks = read_conf(path)
          if old_chunks != chunks:
              to_reboot = True
      if remove_seed_marker:
          lxcret = run_cmd(
              name, 'rm -f \"{0}\"'.format(SEED_MARKER),
              stdout=False, stderr=False)
  
      # last time to be sure any of our property is correctly applied
      cfg = _LXCConfig(name=name, nic=nic, nic_opts=nic_opts,
                       bridge=bridge, gateway=gateway,
                       autostart=autostart,
                       cpuset=cpuset, cpushare=cpushare, memory=memory)
      old_chunks = []
      if os.path.exists(cfg.path):
          old_chunks = read_conf(cfg.path)
      cfg.write()
      chunks = read_conf(cfg.path)
      if old_chunks != chunks:
          comment += 'Container configuration updated\n'
          to_reboot = True
      else:
          if not to_reboot:
              comment += 'Container already correct\n'
      if to_reboot:
          stop(name)
      if clone_from:
          inner = 'cloned'
          comment += 'Container cloned\n'
      else:
          inner = 'created'
          comment += 'Container created\n'
      ret[inner] = True
      if (
          not does_exist
          or (
              does_exist
              and state(name) != 'running'
          )
      ):
          ret['state'] = start(name)
      ret['state'] = state(name)
  
      # set the default user/password, only the first time
      if password:
          changes['250_password'] = 'Passwords in place\n'
          gid = '/.lxc.initial_pass'
          gids = [gid,
                  '/lxc.initial_pass',
                  '/.lxc.{0}.initial_pass'.format(name)]
          lxcrets = []
          for ogid in gids:
              lxcrets.append(
                  bool(run_cmd(
                      name, 'test -e {0}'.format(gid),
                      stdout=False, stderr=False)))
          if True not in lxcrets:
              cret = set_pass(name,
                                              password=password, users=users)
              changes['250_password'] = 'Password updated\n'
              if not cret['result']:
                  ret['result'] = False
                  changes['250_password'] = 'Failed to update passwords\n'
              try:
                  lxcret = int(
                      run_cmd(
                          name,
                          'sh -c \'touch "{0}"; '
                          'test -e "{0}";echo ${{?}}\''.format(gid)))
              except ValueError:
                  lxcret = 1
              ret['result'] = not bool(lxcret)
              if not cret['result']:
                  changes['250_password'] = 'Failed to test password file marker'
          comment += changes['250_password']
          if not ret['result']:
              ret['comment'] = comment
              return ret
  
      # set dns servers if any, only the first time
      if dnsservers:
          changes['350_dns'] = 'DNS in place\n'
          # retro compatibility, test also old markers
          gid = '/.lxc.initial_dns'
          gids = [gid,
                  '/lxc.initial_dns',
                  '/lxc.{0}.initial_dns'.format(name)]
          lxcrets = []
          for ogid in gids:
              lxcrets.append(bool(
                  run_cmd(
                      name, 'test -e {0}'.format(ogid),
                      stdout=False, stderr=False)))
          if True not in lxcrets:
              cret = set_dns(name, dnsservers=dnsservers)
              changes['350_dns'] = 'DNS updated\n'
              if not cret['result']:
                  ret['result'] = False
                  changes['350_dns'] = 'DNS provisioning error\n'
              try:
                  lxcret = int(
                      run_cmd(
                          name,
                          'sh -c \'touch "{0}"; '
                          'test -e "{0}";echo ${{?}}\''.format(gid)))
              except ValueError:
                  lxcret = 1
              ret['result'] = not lxcret
              if not cret['result']:
                  changes['350_dns'] = 'Failed to set DNS marker\n'
          comment += changes['350_dns']
          if not ret['result']:
              ret['comment'] = comment
              return ret
  
      if seed or seed_cmd:
          changes['450_seed'] = 'Container seeded\n'
          if seed:
              ret['seeded'] = bootstrap(
                  name, config=salt_config,
                  approve_key=approve_key,
                  pub_key=pub_key, priv_key=priv_key,
                  install=install,
                  force_install=force_install,
                  unconditional_install=unconditional_install,
                  bootstrap_url=bootstrap_url,
                  bootstrap_shell=bootstrap_shell,
                  bootstrap_args=bootstrap_args)
          elif seed_cmd:
              lxc_info = info(name)
              rootfs = lxc_info['rootfs']
              ret['seeded'] = __salt__[seed_cmd](rootfs, name, salt_config)
          if not ret['seeded']:
              ret['result'] = False
              changes['450_seed'] = 'Seeding error\n'
          comment += changes['450_seed']
          if not ret['seeded']:
              ret['comment'] = comment
              ret['result'] = False
              return ret
      else:
          ret['seeded'] = True
  
      if not start_:
          stop(name)
          ret['state'] = 'stopped'
          comment += 'Container stopped\n'
      else:
          ret['state'] = state(name)
      ret['comment'] = comment
      ret['mid'] = name
      return ret
  
  
  def cloud_init(name, vm_=None, **kwargs):
      '''
      Thin wrapper to lxc.init to be used from the saltcloud lxc driver
  
      name
          Name of the container
          may be None and then guessed from saltcloud mapping
      `vm_`
          saltcloud mapping defaults for the vm
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.cloud_init foo
      '''
      init_interface = cloud_init_interface(name, vm_, **kwargs)
      name = init_interface.pop('name', name)
      return init(name, **init_interface)
  
  
  def create(name, config=None, profile=None, options=None, **kwargs):
      '''
      Create a new container.
  
      name
          Name of the container.
      config
          The config file to use for the container. Defaults to system-wide
          config (usually in /etc/lxc/lxc.conf).
      profile
          A LXC profile (defined in config or pillar).
      template
          The template to use. E.g., 'ubuntu' or 'fedora'.
      backing
          The type of storage to use. Set to 'lvm' to use an LVM group. Defaults
          to filesystem within /var/lib/lxc/.
      vgname
          Name of the LVM volume group in which to create the volume for this
          container. Only applicable if backing=lvm. Defaults to 'lxc'.
      fstype
          fstype to use on LVM lv.
      size
          Size of the volume to create. Only applicable if backing=lvm.
          Defaults to 1G.
      options
          Template specific options to pass to the lxc-create command.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.create name [config=config_file] \\
                  [profile=profile] [template=template_name] \\
                  [backing=backing_store] [vgname=volume_group] \\
                  [size=filesystem_size] [options=template_options]
  
      '''
      kwargs = copy.deepcopy(kwargs)
      if exists(name):
          return {'created': False, 'error': 'container already exists'}
  
      cmd = 'lxc-create -n {0}'.format(name)
  
      if not isinstance(profile, dict):
          profile = _lxc_profile(profile)
      profile = copy.deepcopy(profile)
  
      def select(k, default=None):
          kw = kwargs.pop(k, _marker)
          p = profile.pop(k, default)
          # let kwargs be really be the preferred choice
          if kw is _marker:
              kw = p
          return kw
  
      tvg = select('vgname')
      vgname = tvg if tvg else __salt__['config.get']('lxc.vgname')
      template = select('template')
      backing = select('backing')
      if vgname and not backing:
          backing = 'lvm'
      lvname = select('lvname')
      fstype = select('fstype')
      size = select('size', '1G')
      image = select('image')
      if backing in ['dir', 'overlayfs', 'btrfs']:
          fstype = None
          size = None
      # some backends wont support some parameters
      if backing in ['aufs', 'dir', 'overlayfs', 'btrfs']:
          lvname = vgname = None
  
      if image:
          img_tar = __salt__['cp.cache_file'](image)
          template = os.path.join(
                  os.path.dirname(salt.__file__),
                  'templates',
                  'lxc',
                  'salt_tarball')
          profile['imgtar'] = img_tar
      if config:
          cmd += ' -f {0}'.format(config)
      if template:
          cmd += ' -t {0}'.format(template)
      if backing:
          backing = backing.lower()
          cmd += ' -B {0}'.format(backing)
          if backing in ['lvm']:
              if lvname:
                  cmd += ' --lvname {0}'.format(vgname)
              if vgname:
                  cmd += ' --vgname {0}'.format(vgname)
          if backing not in ['dir', 'overlayfs']:
              if fstype:
                  cmd += ' --fstype {0}'.format(fstype)
              if size:
                  cmd += ' --fssize {0}'.format(size)
      options = options or {}
      if profile:
          profile.update(options)
          options = profile
  
      if options:
          cmd += ' --'
          for k, v in options.items():
              cmd += ' --{0} {1}'.format(k, v)
  
      ret = __salt__['cmd.run_all'](cmd)
      if ret['retcode'] == 0 and exists(name):
          return {'created': True}
      else:
          if exists(name):
              # destroy the container if it was partially created
              cmd = 'lxc-destroy -n {0}'.format(name)
              __salt__['cmd.retcode'](cmd)
          log.warn('lxc-create failed to create container')
          return {'created': False, 'error':
                  'container could not be created with cmd "{0}": {1}'.format(cmd, ret['stderr'])}
  
  
  def clone(name,
            orig,
            snapshot=False,
            profile=None,
            **kwargs):
      '''
      Clone a container.
  
      name
          Name of the container.
      orig
          Name of the cloned original container
      snapshot
          Do we use Copy On Write snapshots (LVM)
      size
          Size of the container
      vgname
          LVM volume group(lxc)
      profile
          A LXC profile (defined in config or pillar).
  
      CLI Examples:
  
      .. code-block:: bash
  
          salt '*' lxc.clone myclone ubuntu "snapshot=True"
          salt 'minion' lxc.clone name orig [snapshot=(True|False)] \\
                  [size=filesystem_size] [vgname=volume_group] \\
                  [profile=profile_name]
      '''
      if not isinstance(profile, dict):
          profile = _lxc_profile(profile)
      kwargs = copy.deepcopy(kwargs)
      profile = copy.deepcopy(profile)
      if exists(name):
          return {'cloned': False, 'error': 'container already exists'}
  
      orig_state = state(orig)
      if orig_state is None:
          return {'cloned': False,
                  'error':
                  'original container \'{0}\' does not exist'.format(orig)}
      elif orig_state != 'stopped':
          return {'cloned': False,
                  'error': 'original container \'{0}\' is running'.format(orig)}
  
      def select(k, default=None):
          kw = kwargs.pop(k, _marker)
          p = profile.pop(k, default)
          # let kwargs be really be the preferred choice
          if kw is _marker:
              kw = p
          return kw
  
      backing = select('backing')
      if backing in ['dir']:
          snapshot = False
      if not snapshot:
          snapshot = ''
      else:
          snapshot = '-s'
  
      cmd = 'lxc-clone {2} -o {0} -n {1}'.format(orig, name, snapshot)
      size = select('size', '1G')
      if backing in ['dir', 'overlayfs']:
          size = None
      if size:
          cmd += ' -L {0}'.format(size)
      if backing:
          cmd += ' -B {0}'.format(backing)
  
      ret = __salt__['cmd.run_all'](cmd)
      if ret['retcode'] == 0 and exists(name):
          return {'cloned': True}
      else:
          if exists(name):
              # destroy the container if it was partially created
              cmd = 'lxc-destroy -n {0}'.format(name)
              __salt__['cmd.retcode'](cmd)
          log.warn('lxc-clone failed to create container')
          return {'cloned': False, 'error': (
              'container could not be created'
              ' with cmd "{0}": {1}'
          ).format(cmd, ret['stderr'])}
  
  
  def ls_():
      '''
      Return just a list of the containers available
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.ls
      '''
      return __salt__['cmd.run']('lxc-ls | sort -u').splitlines()
  
  
  def list_(extra=False):
      '''
      List defined containers classified by status.
      Status can be running, stopped, and frozen.
  
      extra
          Also get per container specific info at once.
  
          .. warning::
  
              It will not return a collection of list
              but a collection of mappings by status and then per
              container name:
  
              .. code-block:: python
  
                  {'running': ['foo']} # normal mode
                  {'running': {'foo': {'info1': 'bar'}} # extra mode
  
      CLI Examples:
  
      .. code-block:: bash
  
          salt '*' lxc.list
          salt '*' lxc.list extra=True
      '''
      ctnrs = __salt__['cmd.run']('lxc-ls | sort -u').splitlines()
  
      if extra:
          stopped = {}
          frozen = {}
          running = {}
      else:
          stopped = []
          frozen = []
          running = []
  
      ret = {'running': running,
             'stopped': stopped,
             'frozen': frozen}
  
      for container in ctnrs:
          c_infos = __salt__['cmd.run'](
                  'lxc-info -n {0}'.format(container)).splitlines()
          log.debug(c_infos)
          c_state = None
          for c_info in c_infos:
              log.debug(c_info)
              stat = c_info.split(':')
              if stat[0] in ('State', 'state'):
                  c_state = stat[1].strip()
                  break
          if extra:
              try:
                  infos = info(container)
              except Exception:
                  trace = traceback.format_exc()
                  infos = {'error': 'Error while getting extra infos',
                           'comment': trace}
              method = 'update'
              value = {container: infos}
          else:
              method = 'append'
              value = container
  
          if not c_state:
              continue
          if c_state == 'STOPPED':
              getattr(stopped, method)(value)
              continue
          if c_state == 'FROZEN':
              getattr(frozen, method)(value)
              continue
          if c_state == 'RUNNING':
              getattr(running, method)(value)
              continue
      return ret
  
  
  def _change_state(cmd, name, expected):
      s1 = state(name)
      if s1 is None:
          return {'state': None, 'change': False}
      elif s1 == expected:
          return {'state': expected, 'change': False}
  
      cmd = '{0} -n {1}'.format(cmd, name)
      err = __salt__['cmd.run_stderr'](cmd)
      if err:
          s2 = state(name)
          r = {'state': s2, 'change': s1 != s2, 'error': err}
      else:
          if expected is not None:
              # some commands do not wait, so we will
              cmd = 'lxc-wait -n {0} -s {1}'.format(name, expected.upper())
              __salt__['cmd.run'](cmd, timeout=30)
          s2 = state(name)
          r = {'state': s2, 'change': s1 != s2}
      return r
  
  
  def _ensure_running(name, no_start=False):
      '''
      If the container is not currently running, start it. This function returns
      the state that the container was in before changing
      '''
      prior_state = state(name)
      if not prior_state:
          return None
      res = {}
      if prior_state == 'stopped':
          if no_start:
              return False
          res = start(name)
      elif prior_state == 'frozen':
          if no_start:
              return False
          res = unfreeze(name)
      if res.get('error'):
          log.warn('Failed to run command: {0}'.format(res['error']))
          return False
      return prior_state
  
  
  def start(name, restart=False):
      '''
      Start the named container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.start name
      '''
      ret = {'name': name,
             'changes': {},
             'result': True,
             'comment': 'Started'}
      try:
          does_exist = exists(name)
          if not does_exist:
              return {'name': name,
                      'result': False,
                      'comment': 'Container does not exist'}
          if restart:
              stop(name)
          ret.update(_change_state('lxc-start -d', name, 'running'))
          infos = info(name)
          ret['result'] = infos['state'] == 'running'
          if ret['change']:
              ret['changes']['started'] = 'started'
      except Exception as ex:
          trace = traceback.format_exc()
          ret['result'] = False
          ret['comment'] = 'Error in starting container'
          ret['comment'] += '{0}\n{1}\n'.format(ex, trace)
      return ret
  
  
  def stop(name, kill=True):
      '''
      Stop the named container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.stop name
      '''
      cmd = 'lxc-stop'
      if kill:
          cmd += ' -k'
      ret = {'name': name,
             'changes': {},
             'result': True,
             'comment': 'Stopped'}
      try:
          does_exist = exists(name)
          if not does_exist:
              return {'name': name,
                      'result': False,
                      'changes': {},
                      'comment': 'Container does not exist'}
          ret.update(_change_state('lxc-stop', name, 'stopped'))
          infos = info(name)
          ret['result'] = infos['state'] == 'stopped'
          if ret['change']:
              ret['changes']['stopped'] = 'stopped'
      except Exception as ex:
          trace = traceback.format_exc()
          ret['result'] = False
          ret['comment'] = 'Error in stopping container'
          ret['comment'] += '{0}\n{1}\n'.format(ex, trace)
      return ret
  
  
  def freeze(name):
      '''
      Freeze the named container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.freeze name
      '''
      return _change_state('lxc-freeze', name, 'frozen')
  
  
  def unfreeze(name):
      '''
      Unfreeze the named container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.unfreeze name
      '''
      return _change_state('lxc-unfreeze', name, 'running')
  
  
  def destroy(name, stop=True):
      '''
      Destroy the named container.
      WARNING: Destroys all data associated with the container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.destroy name [stop=(True|False)]
      '''
      if stop:
          _change_state('lxc-stop -k', name, 'stopped')
      return _change_state('lxc-destroy', name, None)
  
  
  def exists(name):
      '''
      Returns whether the named container exists.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.exists name
      '''
      l = list_()
      return name in l['running'] + l['stopped'] + l['frozen']
  
  
  def state(name):
      '''
      Returns the state of a container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.state name
      '''
      if not exists(name):
          return None
  
      cmd = 'lxc-info -n {0}'.format(name)
      ret = __salt__['cmd.run_all'](cmd)
      if ret['retcode'] != 0:
          return False
      else:
          c_infos = ret['stdout'].splitlines()
          c_state = None
          for c_info in c_infos:
              stat = c_info.split(':')
              if stat[0] in ('State', 'state'):
                  c_state = stat[1].strip().lower()
                  break
          return c_state
  
  
  def get_parameter(name, parameter):
      '''
      Returns the value of a cgroup parameter for a container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.get_parameter name parameter
      '''
      if not exists(name):
          return None
  
      cmd = 'lxc-cgroup -n {0} {1}'.format(name, parameter)
      ret = __salt__['cmd.run_all'](cmd)
      if ret['retcode'] != 0:
          return False
      else:
          return {parameter: ret['stdout'].strip()}
  
  
  def set_parameter(name, parameter, value):
      '''
      Set the value of a cgroup parameter for a container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.set_parameter name parameter value
      '''
      if not exists(name):
          return None
  
      cmd = 'lxc-cgroup -n {0} {1} {2}'.format(name, parameter, value)
      ret = __salt__['cmd.run_all'](cmd)
      if ret['retcode'] != 0:
          return False
      else:
          return True
  
  
  def templates(templates_dir='/usr/share/lxc/templates'):
      '''
      Returns a list of existing templates
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.templates
      '''
      templates_list = []
      san = re.compile('^lxc-')
      if os.path.isdir(templates_dir):
          templates_list.extend(
              [san.sub('', a) for a in os.listdir(templates_dir)]
          )
      templates_list.sort()
      return templates_list
  
  
  def info(name):
      '''
      Returns information about a container.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.info name
      '''
      f = '/var/lib/lxc/{0}/config'.format(name)
      if not os.path.isfile(f):
          return None
  
      ret = {}
  
      with salt.utils.fopen(f) as fhr:
          config = [(v[0].strip(), v[1].strip()) for v in
                    [l.split('#', 1)[0].strip().split('=', 1) for l in
                     fhr.readlines()] if len(v) == 2]
  
      ifaces = []
      current = None
  
      for k, v in config:
          if k == 'lxc.network.type':
              current = {'type': v}
              ifaces.append(current)
          elif not current:
              continue
          elif k.startswith('lxc.network.'):
              current[k.replace('lxc.network.', '', 1)] = v
      if ifaces:
          ret['nics'] = ifaces
  
      ret['rootfs'] = next((i[1] for i in config if i[0] == 'lxc.rootfs'), None)
      ret['state'] = state(name)
      ret['_ips'] = []
      ret['public_ips'] = []
      ret['private_ips'] = []
      ret['public_ipv4_ips'] = []
      ret['public_ipv6_ips'] = []
      ret['private_ipv4_ips'] = []
      ret['private_ipv6_ips'] = []
      ret['ipv4_ips'] = []
      ret['ipv6_ips'] = []
      ret['size'] = None
      ret['config'] = f
  
      if ret['state'] == 'running':
          try:
              limit = int(get_parameter(name, 'memory.limit_in_bytes').get(
                  'memory.limit_in_bytes'))
          except (TypeError, ValueError):
              limit = 0
          try:
              usage = int(get_parameter(name, 'memory.usage_in_bytes').get(
                  'memory.usage_in_bytes'))
          except (TypeError, ValueError):
              usage = 0
          free = limit - usage
          ret['memory_limit'] = limit
          ret['memory_free'] = free
          ret['size'] = __salt__['cmd.run'](
              ('lxc-attach --clear-env -n {0} -- '
               'df /|tail -n1|awk \'{{print $2}}\'').format(pipes.quote(name)))
          ipaddr = __salt__['cmd.run'](
              'lxc-attach --clear-env -n {0} -- ip addr show'.format(pipes.quote(name)))
          for line in ipaddr.splitlines():
              if 'inet' in line:
                  line = line.split()
                  ip_address = line[1].split('/')[0]
                  if ip_address not in ret['_ips']:
                      ret['_ips'].append(ip_address)
                      if '::' in ip_address:
                          ret['ipv6_ips'].append(ip_address)
                          if (
                              ip_address == '::1'
                              or ip_address.startswith('fe80')
                          ):
                              ret['private_ips'].append(ip_address)
                              ret['private_ipv6_ips'].append(ip_address)
                          else:
                              ret['public_ips'].append(ip_address)
                              ret['public_ipv6_ips'].append(ip_address)
                      else:
                          ret['ipv4_ips'].append(ip_address)
                          if ip_address == '127.0.0.1':
                              ret['private_ips'].append(ip_address)
                              ret['private_ipv4_ips'].append(ip_address)
                          elif salt.utils.cloud.is_public_ip(ip_address):
                              ret['public_ips'].append(ip_address)
                              ret['public_ipv4_ips'].append(ip_address)
                          else:
                              ret['private_ips'].append(ip_address)
                              ret['private_ipv4_ips'].append(ip_address)
      for k in [l for l in ret if l.endswith('_ips')]:
          ret[k].sort(key=_ip_sort)
      return ret
  
  
  def set_pass(name, users, password):
      '''Set the password of one or more system users inside containers
  
      CLI Example:
  
      .. code-block:: bash
  
          salt '*' lxc.set_pass container-name root foo
  
      '''
      ret = {'result': True, 'comment': ''}
      if not isinstance(users, list):
          users = [users]
      ret['comment'] = ''
      if users:
          try:
              cmd = (
                  "lxc-attach --clear-env -n {0} -- "
                  " sh -c \""
                  "").format(pipes.quote(name))
              for i in users:
                  cmd += "echo {0}:{1}|chpasswd && ".format(
                      pipes.quote(i),
                      pipes.quote(password),
                  )
              cmd += " true\""
              cret = __salt__['cmd.run_all'](cmd)
              if cret['retcode'] != 0:
                  raise ValueError('Can\'t change passwords')
              ret['comment'] = 'Password updated for {0}'.format(users)
          except ValueError as ex:
              trace = traceback.format_exc()
              ret['result'] = False
              ret['comment'] = 'Error in setting base password\n'
              ret['comment'] += '{0}\n{1}\n'.format(ex, trace)
      else:
          ret['result'] = False
          ret['comment'] = 'No user selected'
      return ret
  
  
  def update_lxc_conf(name, lxc_conf, lxc_conf_unset):
      '''Edit LXC configuration options
  
      CLI Example:
  
      .. code-block:: bash
  
          salt-call -lall lxc.update_lxc_conf ubuntu \\
                  lxc_conf="[{'network.ipv4.ip':'10.0.3.5'}]" \\
                  lxc_conf_unset="['lxc.utsname']"
  
      '''
      changes = {'edited': [], 'added': [], 'removed': []}
      ret = {'changes': changes, 'result': True, 'comment': ''}
      lxc_conf_p = '/var/lib/lxc/{0}/config'.format(name)
      if not exists(name):
          ret['result'] = False
          ret['comment'] = 'Container does not exist: {0}'.format(name)
      elif not os.path.exists(lxc_conf_p):
          ret['result'] = False
          ret['comment'] = (
              'Configuration does not exist: {0}'.format(lxc_conf_p))
      else:
          with salt.utils.fopen(lxc_conf_p, 'r') as fic:
              filtered_lxc_conf = []
              for row in lxc_conf:
                  if not row:
                      continue
                  for conf in row:
                      try:
                          filtered_lxc_conf.append((conf.strip(),
                                                    row[conf].strip()))
                      except AttributeError:
                          filtered_lxc_conf.append((conf.strip(),
                                                    str(row[conf]).strip()))
              ret['comment'] = 'lxc.conf is up to date'
              lines = []
              orig_config = fic.read()
              for line in orig_config.splitlines():
                  if line.startswith('#') or not line.strip():
                      lines.append([line, ''])
                  else:
                      line = line.split('=')
                      index = line.pop(0)
                      val = (index.strip(), '='.join(line).strip())
                      if val not in lines:
                          lines.append(val)
              for k, item in filtered_lxc_conf:
                  matched = False
                  for idx, line in enumerate(lines[:]):
                      if line[0] == k:
                          matched = True
                          lines[idx] = (k, item)
                          if '='.join(line[1:]).strip() != item.strip():
                              changes['edited'].append(
                                  ({line[0]: line[1:]}, {k: item}))
                              break
                  if not matched:
                      if not (k, item) in lines:
                          lines.append((k, item))
                      changes['added'].append({k: item})
              dest_lxc_conf = []
              # filter unset
              if lxc_conf_unset:
                  for line in lines:
                      for opt in lxc_conf_unset:
                          if (
                              not line[0].startswith(opt)
                              and line not in dest_lxc_conf
                          ):
                              dest_lxc_conf.append(line)
                          else:
                              changes['removed'].append(opt)
              else:
                  dest_lxc_conf = lines
              conf = ''
              for k, val in dest_lxc_conf:
                  if not val:
                      conf += '{0}\n'.format(k)
                  else:
                      conf += '{0} = {1}\n'.format(k.strip(), val.strip())
              conf_changed = conf != orig_config
              chrono = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
              if conf_changed:
                  with salt.utils.fopen('{0}.{1}'.format(lxc_conf_p, chrono), 'w') as wfic:
                      wfic.write(conf)
                  with salt.utils.fopen(lxc_conf_p, 'w') as wific:
                      wfic.write(conf)
                  ret['comment'] = 'Updated'
                  ret['result'] = True
      if (
          not changes['added']
          and not changes['edited']
          and not changes['removed']
      ):
          ret['changes'] = {}
      return ret
  
  
  def set_dns(name, dnsservers=None, searchdomains=None):
      '''
      Update container DNS configuration
      and possibly also resolv.conf one.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt-call -lall lxc.set_dns ubuntu ['8.8.8.8', '4.4.4.4']
  
      '''
      ret = {'result': False}
      if not dnsservers:
          dnsservers = ['8.8.8.8', '4.4.4.4']
      if not searchdomains:
          searchdomains = []
      dns = ['nameserver {0}'.format(d) for d in dnsservers]
      dns.extend(['search {0}'.format(d) for d in searchdomains])
      dns = "\n".join(dns)
      has_resolvconf = not int(
          __salt__['cmd.run'](('lxc-attach --clear-env -n {0} -- '
                               'test -e /etc/resolvconf/resolv.conf.d/base;'
                               'echo ${{?}}').format(pipes.quote(name))))
      if has_resolvconf:
          cret = __salt__['cmd.run_all']((
              'lxc-attach --clear-env -n {0} -- '
              'rm /etc/resolvconf/resolv.conf.d/base &&'
              'echo {1}|lxc-attach --clear-env -n {0} -- '
              'tee /etc/resolvconf/resolv.conf.d/base'
          ).format(pipes.quote(name), pipes.quote(dns)))
          if not cret['retcode']:
              ret['result'] = True
      cret = __salt__['cmd.run_all']((
          'lxc-attach --clear-env -n {0} -- rm /etc/resolv.conf &&'
          'echo {1}|lxc-attach --clear-env -n {0} -- '
          'tee /etc/resolv.conf'
      ).format(pipes.quote(name), pipes.quote(dns)))
      if not cret['retcode']:
          ret['result'] = True
      return ret
  
  
  def bootstrap(name, config=None, approve_key=True,
                install=True,
                pub_key=None, priv_key=None,
                bootstrap_url=None,
                force_install=False,
                unconditional_install=False,
                bootstrap_args=None,
                bootstrap_shell=None):
      '''
      Install and configure salt in a container.
  
      config
          Minion configuration options. By default, the ``master`` option is set
          to the target host's master.
  
      approve_key
          Request a pre-approval of the generated minion key. Requires
          that the salt-master be configured to either auto-accept all keys or
          expect a signing request from the target host. Default: ``True``
  
  
      pub_key
          Explicit public key to pressed the minion with (optional).
          This can be either a filepath or a string representing the key
  
      priv_key
          Explicit private key to pressed the minion with (optional).
          This can be either a filepath or a string representing the key
  
      bootstrap_url
          url, content or filepath to the salt bootstrap script
  
      bootstrap_args
          salt bootstrap script arguments
  
      bootstrap_shell
          shell to execute the script into
  
      install
          Whether to attempt a full installation of salt-minion if needed.
  
      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 Examples:
  
      .. code-block:: bash
  
          salt '*' lxc.bootstrap ubuntu
          salt 'minion' lxc.bootstrap name [config=config_data] \\
                  [approve_key=(True|False)] [install=(True|False)]
      '''
  
      infos = info(name)
      if not infos:
          return None
      # default set here as we cannot set them
      # in def as it can come from a chain of procedures.
      if not bootstrap_args:
          bootstrap_args = '-c {0}'
      if not bootstrap_shell:
          bootstrap_shell = 'sh'
  
      prior_state = _ensure_running(name)
      if not prior_state:
          return prior_state
  
      cmd = 'bash -c "if type salt-minion; then ' \
            'salt-call --local service.stop salt-minion; exit 0; ' \
            'else exit 1; fi"'
      if not force_install:
          # no need to run this cmd in force mode
          needs_install = bool(run_cmd(name, cmd, stdout=False))
      else:
          needs_install = True
      seeded = not run_cmd(
          name,
          'test -e \"{0}\"'.format(SEED_MARKER),
          stdout=False, stderr=False)
      tmp = tempfile.mkdtemp()
      if seeded and not unconditional_install:
          res = True
      else:
          res = False
          cfg_files = __salt__['seed.mkconfig'](
              config, tmp=tmp, id_=name, approve_key=approve_key,
              priv_key=priv_key, pub_key=pub_key)
          if needs_install or force_install or unconditional_install:
              if install:
                  rstr = __salt__['test.rand_str']()
                  configdir = '/tmp/.c_{0}'.format(rstr)
                  run_cmd(name, 'install -m 0700 -d {0}'.format(configdir))
                  bs_ = __salt__['config.gather_bootstrap_script'](
                      bootstrap=bootstrap_url)
                  dest_dir = os.path.join('/tmp', rstr)
                  for cmd in [
                      'mkdir -p {0}'.format(dest_dir),
                      'chmod 700 {0}'.format(dest_dir),
                  ]:
                      if run_cmd(name, cmd, stdout=True):
                          log.error(
                              ('tmpdir {0} creation'
                               ' failed ({1}').format(dest_dir, cmd))
                          return False
                  cp(name,
                     bs_,
                     '{0}/bootstrap.sh'.format(dest_dir))
                  cp(name, cfg_files['config'],
                     os.path.join(configdir, 'minion'))
                  cp(name, cfg_files['privkey'],
                     os.path.join(configdir, 'minion.pem'))
                  cp(name, cfg_files['pubkey'],
                     os.path.join(configdir, 'minion.pub'))
                  bootstrap_args = bootstrap_args.format(configdir)
                  cmd = ("{0} -c 'PATH=$PATH:/bin:/sbin:/usr/sbin"
                         " {0} {2}/bootstrap.sh {1}'").format(
                             bootstrap_shell,
                             bootstrap_args.replace("'", "''"),
                             dest_dir)
                  # log ASAP the forged bootstrap command which can be wrapped
                  # out of the output in case of unexpected problem
                  log.info('Running {0} in lxc {1}'.format(cmd, name))
                  res = not run_cmd(
                      name, cmd,
                      stdout=True, stderr=True, use_vt=True)['retcode']
              else:
                  res = False
          else:
              minion_config = salt.config.minion_config(cfg_files['config'])
              pki_dir = minion_config['pki_dir']
              cp(name, cfg_files['config'], '/etc/salt/minion')
              cp(name, cfg_files['privkey'], os.path.join(pki_dir, 'minion.pem'))
              cp(name, cfg_files['pubkey'], os.path.join(pki_dir, 'minion.pub'))
              run_cmd(name, 'salt-call --local service.enable salt-minion',
                      stdout=False)
              res = True
          shutil.rmtree(tmp)
          if prior_state == 'stopped':
              stop(name)
          elif prior_state == 'frozen':
              freeze(name)
          # mark seeded upon successful install
          if res:
              run_cmd(
                  name, 'sh -c \'touch "{0}";\''.format(SEED_MARKER))
      return res
  
  
  def attachable(name):
      '''
      Return True if the named container can be attached to via the lxc-attach
      command
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.attachable ubuntu
      '''
      cmd = 'lxc-attach -n {0} -- true'.format(pipes.quote(name))
      data = __salt__['cmd.run_all'](cmd)
      if not data['retcode']:
          return True
      if data['stderr'].startswith('lxc-attach: failed to get the init pid'):
          return False
      return False
  
  
  def run_cmd(name, cmd, no_start=False, preserve_state=True,
              stdout=True, stderr=False, use_vt=False,
              keep_env='http_proxy,https_proxy,no_proxy'):
      '''
      Run a command inside the container.
  
      name
          Name of the container on which to operate.
      cmd
          Command to run
      no_start
          If the container is not running, don't start it. Default: ``False``
      preserve_state
          After running the command, return the container to its previous
          state. Default: ``True``
      stdout:
          Return stdout. Default: ``True``
      stderr:
          Return stderr. Default: ``False``
      use_vt
          use saltstack utils.vt to stream output to console
      keep_env
          A list of env vars to preserve. May be passed as comma-delimited list.
          Defaults to http_proxy,https_proxy.
  
      .. note::
  
          If stderr and stdout are both ``False``, the return code is returned.
          If stderr and stdout are both ``True``, the pid and return code are
          also returned.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' name command [no_start=(True|False)] \\
                  [preserve_state=(True|False)] [stdout=(True|False)] \\
                  [stderr=(True|False)]
  
      '''
      prior_state = _ensure_running(name, no_start=no_start)
      if not prior_state:
          return prior_state
      if attachable(name):
          if isinstance(keep_env, six.string_types):
              keep_env = keep_env.split(',')
          env = ' '.join('--set-var {0}={1}'.format(
                         x, pipes.quote(os.environ[x]))
                         for x in keep_env if x in os.environ)
          cmd = 'lxc-attach --clear-env {0} -n {1} -- {2}'.format(env, pipes.quote(name), cmd)
  
          if not use_vt:
              res = __salt__['cmd.run_all'](cmd)
          else:
              stdout, stderr = '', ''
              try:
                  proc = vt.Terminal(cmd,
                                     shell=True,
                                     log_stdin_level='info',
                                     log_stdout_level='info',
                                     log_stderr_level='info',
                                     log_stdout=True,
                                     log_stderr=True,
                                     stream_stdout=True,
                                     stream_stderr=True)
                  # consume output
                  while proc.has_unread_data:
                      try:
                          time.sleep(0.5)
                          try:
                              cstdout, cstderr = proc.recv()
                          except IOError:
                              cstdout, cstderr = '', ''
                          if cstdout:
                              stdout += cstdout
                          else:
                              cstdout = ''
                          if cstderr:
                              stderr += cstderr
                          else:
                              cstderr = ''
                      except KeyboardInterrupt:
                          break
                  res = {'retcode': proc.exitstatus,
                         'pid': 2,
                         'stdout': stdout,
                         'stderr': stderr}
              except vt.TerminalException:
                  trace = traceback.format_exc()
                  log.error(trace)
                  res = {'retcode': 127,
                         'pid': '2',
                         'stdout': stdout,
                         'stderr': stderr}
              finally:
                  proc.close(terminate=True, kill=True)
      else:
          rootfs = info(name).get('rootfs')
          res = __salt__['cmd.run_chroot'](rootfs, cmd)
  
      if preserve_state:
          if prior_state == 'stopped':
              stop(name)
          elif prior_state == 'frozen':
              freeze(name)
  
      if stdout and stderr:
          return res
      elif stdout:
          return res['stdout']
      elif stderr:
          return res['stderr']
      else:
          return res['retcode']
  
  
  def cp(name, src, dest):
      '''
      Copy a file or directory from the host into a container
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.cp /tmp/foo /root/foo
      '''
  
      if state(name) != 'running':
          return {'error': 'container is not running'}
      if not os.path.exists(src):
          return {'error': 'src does not exist'}
      if not os.path.isfile(src):
          return {'error': 'src must be a regular file'}
      src_dir, src_name = os.path.split(src)
  
      dest_dir, dest_name = os.path.split(dest)
      if run_cmd(name, 'test -d {0}'.format(dest_dir), stdout=False):
          return {'error': '{0} is not a directory on the container'.format(dest_dir)}
      if not dest_name:
          dest_name = src_name
  
      # before touching to existing file which may disturb any running
      # process, check that the md5sum are different
      cmd = 'md5sum {0} 2> /dev/null'.format(src)
      csrcmd5 = __salt__['cmd.run_all'](cmd)
      srcmd5 = csrcmd5['stdout'].split()[0]
  
      cmd = 'lxc-attach --clear-env -n {0} -- md5sum {1} 2> /dev/null'.format(
          pipes.quote(name), dest)
      cdestmd5 = __salt__['cmd.run_all'](cmd)
      if not cdestmd5['retcode']:
          try:
              destmd5 = cdestmd5['stdout'].split()[0]
          except(TypeError, IndexError, IndexError):
              destmd5 = ''
      else:
          destmd5 = ''
      ret = {
          'pid': 2,
          'retcode': '0',
          'stdout': '',
          'stderr': '',
      }
      if srcmd5 != destmd5:
          cmd = 'cat {0} | lxc-attach --clear-env -n {1} -- tee {2} > /dev/null'.format(
              pipes.quote(src), pipes.quote(name), pipes.quote(dest))
          log.info(cmd)
          ret = __salt__['cmd.run_all'](cmd)
      return ret
  
  
  def read_conf(conf_file, out_format='simple'):
      '''
      Read in an LXC configuration file. By default returns a simple, unsorted
      dict, but can also return a more detailed structure including blank lines
      and comments.
  
      CLI Examples:
  
      .. code-block:: bash
  
          salt 'minion' lxc.read_conf /etc/lxc/mycontainer.conf
          salt 'minion' lxc.read_conf /etc/lxc/mycontainer.conf out_format=commented
      '''
      ret_commented = []
      ret_simple = {}
      with salt.utils.fopen(conf_file, 'r') as fp_:
          for line in fp_.readlines():
              if '=' not in line:
                  ret_commented.append(line)
                  continue
              comps = line.split('=')
              value = '='.join(comps[1:]).strip()
              comment = None
              if value.strip().startswith('#'):
                  vcomps = value.strip().split('#')
                  value = vcomps[1].strip()
                  comment = '#'.join(vcomps[1:]).strip()
                  ret_commented.append({comps[0].strip(): {
                      'value': value,
                      'comment': comment,
                  }})
              else:
                  ret_commented.append({comps[0].strip(): value})
                  ret_simple[comps[0].strip()] = value
  
      if out_format == 'simple':
          return ret_simple
      return ret_commented
  
  
  def write_conf(conf_file, conf):
      '''
      Write out an LXC configuration file
  
      This is normally only used internally. The format of the data structure
      must match that which is returned from ``lxc.read_conf()``, with
      ``out_format`` set to ``commented``.
  
      An example might look like:
  
      .. code-block:: python
  
          [
              {'lxc.utsname': '$CONTAINER_NAME'},
              '# This is a commented line\\n',
              '\\n',
              {'lxc.mount': '$CONTAINER_FSTAB'},
              {'lxc.rootfs': {'comment': 'This is another test',
                              'value': 'This is another test'}},
              '\\n',
              {'lxc.network.type': 'veth'},
              {'lxc.network.flags': 'up'},
              {'lxc.network.link': 'br0'},
              {'lxc.network.hwaddr': '$CONTAINER_MACADDR'},
              {'lxc.network.ipv4': '$CONTAINER_IPADDR'},
              {'lxc.network.name': '$CONTAINER_DEVICENAME'},
          ]
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.write_conf /etc/lxc/mycontainer.conf \\
              out_format=commented
      '''
      if not isinstance(conf, list):
          return {'Error': 'conf must be passed in as a list'}
  
      with salt.utils.fopen(conf_file, 'w') as fp_:
          for line in conf:
              if isinstance(line, str):
                  fp_.write(line)
              elif isinstance(line, dict):
                  key = next(iter(list(line.keys())))
                  out_line = None
                  if isinstance(line[key], str):
                      out_line = ' = '.join((key, line[key]))
                  elif isinstance(line[key], dict):
                      out_line = ' = '.join((key, line[key]['value']))
                      if 'comment' in line[key]:
                          out_line = ' # '.join((out_line, line[key]['comment']))
                  if out_line:
                      fp_.write(out_line)
                      fp_.write('\n')
      return {}
  
  
  def edit_conf(conf_file, out_format='simple', **kwargs):
      '''
      Edit an LXC configuration file. If a setting is already present inside the
      file, its value will be replaced. If it does not exist, it will be appended
      to the end of the file. Comments and blank lines will be kept in-tact if
      they already exist in the file.
  
      After the file is edited, its contents will be returned. By default, it
      will be returned in ``simple`` format, meaning an unordered dict (which
      may not represent the actual file order). Passing in an ``out_format`` of
      ``commented`` will return a data structure which accurately represents the
      order and content of the file.
  
      CLI Example:
  
      .. code-block:: bash
  
          salt 'minion' lxc.edit_conf /etc/lxc/mycontainer.conf \\
              out_format=commented lxc.network.type=veth
      '''
      data = []
  
      try:
          conf = read_conf(conf_file, out_format='commented')
      except Exception:
          conf = []
  
      for line in conf:
          if not isinstance(line, dict):
              data.append(line)
              continue
          else:
              key = next(iter(list(line.keys())))
              if key not in kwargs:
                  data.append(line)
                  continue
              data.append({key: kwargs[key]})
              del kwargs[key]
  
      for kwarg in kwargs:
          if kwarg.startswith('__'):
              continue
          data.append({kwarg: kwargs[kwarg]})
  
      write_conf(conf_file, data)
      return read_conf(conf_file, out_format)
  