阿里山QQ

导航

saltstack

一、salt入门

1、saltstack的3种运行方式:

    local

    Master/Minion

    Salt SSH

2、salttstack的3大功能:

    远程执行

    配置管理

    云管理

3、saltstack的基础配置和通信原理

编辑minion的配置文件:/etc/salt/minion,修改master

master: 192.168.74.20

schedule:
  highstate:
    function: state.highstate
    seconds: 30

客户端每隔30s到server端同步一次数据; 

注意:关于主机名的解析

[root@linux-node1 master]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.74.20  linux-node1.example.com  linux-node1
192.168.74.21  linux-node2.example.com  linux-node2 

salt安装完毕之后,首先需要在minion端坐初始化操作;需要指定server的地址以及id(可以不指定,默认为minion的hostname);

 

[root@linux-node1 minion]# pwd
/etc/salt/pki/minion
[root@linux-node1 minion]# ls    #minion.pub是minion的公钥,会拷贝到master端,需要master同意之后才可以实现通信
minion.pem  minion.pub

 

[root@linux-node1 master]# pwd
/etc/salt/pki/master
[root@linux-node1 master]# tree 
.
├── master.pem
├── master.pub
├── minions
├── minions_autosign
├── minions_denied
├── minions_pre
│?? ├── linux-node1.example.com
│?? └── linux-node2.example.com
└── minions_rejected

5 directories, 4 files

 

[root@linux-node1 master]# salt-key 
Accepted Keys:
Denied Keys:
Unaccepted Keys:
linux-node1.example.com
linux-node2.example.com
Rejected Keys:

  

接下来需要同意公钥:

[root@linux-node1 master]# salt-key -a linux*     #支持通配符
The following keys are going to be accepted:
Unaccepted Keys:
linux-node1.example.com
linux-node2.example.com
Proceed? [n/Y] y
Key for minion linux-node1.example.com accepted.
Key for minion linux-node2.example.com accepted.
[root@linux-node1 master]# tree
.
├── master.pem
├── master.pub
├── minions
│?? ├── linux-node1.example.com      #这个是minion的公钥
│?? └── linux-node2.example.com
├── minions_autosign
├── minions_denied
├── minions_pre
└── minions_rejected
[root@linux-node1 minions]# ls
linux-node1.example.com  linux-node2.example.com
[root@linux-node1 minions]# file linux-node1.example.com 
linux-node1.example.com: ASCII text
[root@linux-node1 minions]# cat linux-node1.example.com 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrmgglfYeMpUay3Tx8GG
Pre5gzFe//2jwT8S6tZBA8nyzhOa0ONJSF5aGojdDCb6J6pKZvZOnj3cPdee4oeX
Z4E0bpH7aW2ZpR4yTFaXbJ8xj3TCspVF2of7HMr+eA/CKDojg2NhRGvsUkH+LRry
fCBsHTj/SSUp/k7jH+Yf2gYhT7cRSbKbiEC2V4EsQfIxX4ER7kYgjMUZPdvkRgTY
kgds3Ol4eeL9ZjZguRQen1qI7DQWU9JlhEDerlsnoTreH8XPHBDJ9JvC0BuK4YQm
oyIJUkDY4JFWcCjkecRgVGh9AYHkmofgBaEmf2TKgLrK5lvOK5miViKjc+hVN4zP
2QIDAQAB
-----END PUBLIC KEY-----

 

在minion端,也将master的公钥拿过来了

[root@linux-node2 ~]# cd /etc/salt/pki/
[root@linux-node2 pki]# ls
minion
[root@linux-node2 pki]# cd minion/
[root@linux-node2 minion]# ls
minion_master.pub  minion.pem  minion.pub
[root@linux-node2 minion]# cat minion_master.pub 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0+xX8cUQXZ9eRVMS6J+P
BxgEbB9o2nr7SGhu781RJoYQ2IOT64CsICrx0W/ACbCEqjaRV809VPkGndPfGgu1
IVZ+OP6eVub12ZpOXUpnJRKaVC2v2li6h+OqgCuESitlNpPGtYnbBqcROKv/mtYa
FwLZvIa5aVHUj3UReKky8WAtGCuHHx3TdQQCVJfgkmUG97Y+62wPBbBeop4L0c5d
iO8rICFlty0uMt/0FRmnKj+vN/a/B3DabHWUbf4GUDVevFJCZFZyF24Yhgx4jlu7
+QBRGLjQkKsv+SIR14diIy1Wex3i8f67KtQnlQLlWb2nJmSXiXShAWhccZagXGPF
xwIDAQAB
-----END PUBLIC KEY----- 

配置完成之后,还不能实现master和minion的通信,需要master和minion配置类似于openssh的信任关系,要借助salt-key命令完成,参数-a 可以添加特定的信任主机,-A添加所有的主机,

-D删除所有,-d删除特定的信任主机;

salt-key命令执行完毕之后,minion会将本地的私钥拷贝到master上的/etc/salt/pki/master/minions目录下,同时master会讲自己的公钥拷贝到minion端;

 

二、配置管理

修改/etc/salt/master文件,创建file_roots目录;

file_roots:
  base:
    - /srv/salt

创建/srv/salt目录,然后重启salt-master;

  

下面安装apache,并启动

/srv/salt目录下:
[root@22-57 salt]# cat apache.sls apache-install: #名称 pkg.installed: #pkg是模块的名称,installed是模块中的方法 - names: #names是参数 - httpd - httpd-devel apache-service: service.running: - name: httpd - enable: True - reload: True

 

使用:set list查看不显示的字符:

apache-install:$
  pkg.installed:$
    - names:$
      - httpd$
      - httpd-devel$
$
$
apache-service:$
  service.running:$
    - name: httpd$
    - enable: True$
    - reload: True$

 

 

然后执行salt '*' state.sls apache就会执行上述sls文件: state表示模块,sls为模块中的方法名

 

也可以在top.sls文件中制定上述sls文件,看下面:

[root@22-57 salt]# cat top.sls
base:
  '*':
    - apache

然后执行salt '*' state.highstate就执行apache.sls啦!

 

三、数据系统 Grains

grains中存放着minion启动时获取的系统信息,只有在minion启动的时候才会收集,然后就不变了,只有重启的时候才会重新收集;

grains有三个作用:采集minion数据、在远程执行的时候匹配minion、在top文件执行的时候匹配minion;

 

1、收集信息数据

[root@linux-node1 ~]# salt 'linux-node1*' grains.ls    #将grains的所有key列出来
linux-node1.example.com:
    - SSDs
    - biosreleasedate
    - biosversion
    - cpu_flags
    - cpu_model
    - cpuarch
    - domain
    - fqdn
    - fqdn_ip4
    - fqdn_ip6
    - gpus
    - host
    - hwaddr_interfaces
    - id
    - init
    - ip4_interfaces
    - ip6_interfaces
    - ip_interfaces
    - ipv4
    - ipv6
    - kernel
    - kernelrelease
    - locale_info
    - localhost
    - lsb_distrib_id
    - machine_id
    - manufacturer
    - master
    - mdadm
    - mem_total
    - nodename
    - num_cpus
    - num_gpus
    - os
    - os_family
    - osarch
    - oscodename
    - osfinger
    - osfullname
    - osmajorrelease
    - osrelease
    - osrelease_info
    - path
    - productname
    - ps
    - pythonexecutable
    - pythonpath
    - pythonversion
    - saltpath
    - saltversion
    - saltversioninfo
    - selinux
    - serialnumber
    - server_id
    - shell
    - systemd
    - virtual
    - zmqversion

 

root@linux-node1 ~]# salt 'linux-node1*' grains.items    #将grains的所有内容显示出来
linux-node1.example.com:
    ----------
    SSDs:
    biosreleasedate:
        06/02/2011
    biosversion:
        6.00
    cpu_flags:
        - fpu
        - vme
        - de
        - pse
        - tsc
        - msr
        - pae
        - mce
        - cx8
        - apic
        - sep
        - mtrr
        - pge
        - mca
        - cmov
        - pat
        - pse36
        - clflush
        - dts
        - acpi
        - mmx
        - fxsr
        - sse
        - sse2
        - ss
        - syscall
        - nx
        - rdtscp
        - lm
        - constant_tsc
        - arch_perfmon
        - pebs
        - bts
        - nopl
        - xtopology
        - tsc_reliable
        - nonstop_tsc
        - aperfmperf
        - eagerfpu
        - pni
        - pclmulqdq
        - ssse3
        - fma
        - cx16
        - sse4_1
        - sse4_2
        - movbe
        - popcnt
        - aes
        - xsave
        - avx
        - hypervisor
        - lahf_lm
        - ida
        - arat
        - epb
        - pln
        - pts
        - dtherm
        - hwp
        - hwp_noitfy
        - hwp_act_window
        - hwp_epp
        - xsaveopt
        - xsavec
        - xgetbv1
        - xsaves
    cpu_model:
        Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
    cpuarch:
        x86_64
    domain:
        example.com
    fqdn:
        linux-node1.example.com
    fqdn_ip4:
        - 192.168.74.20
    fqdn_ip6:
    gpus:
        |_
          ----------
          model:
              SVGA II Adapter
          vendor:
              unknown
    host:
        linux-node1
    hwaddr_interfaces:
        ----------
        ens33:
            00:0c:29:89:6a:8f
        lo:
            00:00:00:00:00:00
        virbr0:
            00:00:00:00:00:00
        virbr0-nic:
            52:54:00:95:4d:38
    id:
        linux-node1.example.com
    init:
        systemd
    ip4_interfaces:
        ----------
        ens33:
            - 192.168.74.20
        lo:
            - 127.0.0.1
        virbr0:
            - 192.168.122.1
        virbr0-nic:
    ip6_interfaces:
        ----------
        ens33:
            - fe80::20c:29ff:fe89:6a8f
        lo:
            - ::1
        virbr0:
        virbr0-nic:
    ip_interfaces:
        ----------
        ens33:
            - 192.168.74.20
            - fe80::20c:29ff:fe89:6a8f
        lo:
            - 127.0.0.1
            - ::1
        virbr0:
            - 192.168.122.1
        virbr0-nic:
    ipv4:
        - 127.0.0.1
        - 192.168.122.1
        - 192.168.74.20
    ipv6:
        - ::1
        - fe80::20c:29ff:fe89:6a8f
    kernel:
        Linux
    kernelrelease:
        3.10.0-327.el7.x86_64
    locale_info:
        ----------
        defaultencoding:
            UTF-8
        defaultlanguage:
            en_US
        detectedencoding:
            UTF-8
    localhost:
        linux-node1
    lsb_distrib_id:
        CentOS Linux
    machine_id:
        ae71ba43e74c41a7b705e17fff4a03fb
    manufacturer:
        VMware, Inc.
    master:
        192.168.74.20
    mdadm:
    mem_total:
        1836
    nodename:
        linux-node1
    num_cpus:
        1
    num_gpus:
        1
    os:
        CentOS
    os_family:
        RedHat
    osarch:
        x86_64
    oscodename:
        Core
    osfinger:
        CentOS Linux-7
    osfullname:
        CentOS Linux
    osmajorrelease:
        7
    osrelease:
        7.2.1511
    osrelease_info:
        - 7
        - 2
        - 1511
    path:
        /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
    productname:
        VMware Virtual Platform
    ps:
        ps -efH
    pythonexecutable:
        /usr/bin/python
    pythonpath:
        - /usr/bin
        - /usr/lib/python2.7/site-packages/aliyun_python_sdk_rds-2.0.0-py2.7.egg
        - /usr/lib/python2.7/site-packages/aliyun_python_sdk_core-2.0.36-py2.7.egg
        - /usr/lib64/python27.zip
        - /usr/lib64/python2.7
        - /usr/lib64/python2.7/plat-linux2
        - /usr/lib64/python2.7/lib-tk
        - /usr/lib64/python2.7/lib-old
        - /usr/lib64/python2.7/lib-dynload
        - /usr/lib64/python2.7/site-packages
        - /usr/lib64/python2.7/site-packages/Twisted-16.6.0-py2.7-linux-x86_64.egg
        - /usr/lib64/python2.7/site-packages/constantly-15.1.0-py2.7.egg
        - /usr/lib64/python2.7/site-packages/zope.interface-4.3.3-py2.7-linux-x86_64.egg
        - /root/Twisted-16.6.0/.eggs/incremental-16.10.1-py2.7.egg
        - /usr/lib64/python2.7/site-packages/gtk-2.0
        - /usr/lib/python2.7/site-packages
    pythonversion:
        - 2
        - 7
        - 5
        - final
        - 0
    saltpath:
        /usr/lib/python2.7/site-packages/salt
    saltversion:
        2015.5.10
    saltversioninfo:
        - 2015
        - 5
        - 10
        - 0
    selinux:
        ----------
        enabled:
            True
        enforced:
            Enforcing
    serialnumber:
        VMware-56 4d bd b4 2a f9 c1 e4-53 27 19 67 df 89 6a 8f
    server_id:                       #物理机的快速服务代码
        1981947194
    shell:
        /bin/sh
    systemd:
        ----------
        features:
            +PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ -LZ4 -SECCOMP +BLKID +ELFUTILS +KMOD +IDN
        version:
            219
    virtual:
        VMware
    zmqversion:
        3.2.5

 

[root@linux-node1 ~]# salt 'linux-node1*' grains.item fqdn     #获取其中单独一项
linux-node1.example.com:
    ----------
    fqdn:
        linux-node1.example.com
[root@linux-node1 ~]# salt 'linux-node1*' grains.get fqdn 
linux-node1.example.com:
    linux-node1.example.com

[root@linux-node1 ~]# salt 'linux-node1*' grains.get ip_interfaces:ens33
linux-node1.example.com:
    - 192.168.74.20
    - fe80::20c:29ff:fe89:6a8f

 

系统默认的grains数据都包含在salt项目下的: /salt/grains/core.py下面,有兴趣可以看看;

 

2、匹配minion

[root@linux-node1 ~]# salt 'linux-node1*' grains.get os
linux-node1.example.com:
    CentOS

[root@linux-node1 ~]# salt -G os:Centos cmd.run 'w'    #-G表示匹配grains
linux-node1.example.com:
     21:17:27 up  5:36,  1 user,  load average: 0.16, 0.05, 0.06
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     20:30    7.00s  0.48s  0.36s /usr/bin/python /usr/bin/salt -G os:Centos cmd.run w
linux-node2.example.com:
     21:17:26 up  5:36,  2 users,  load average: 0.02, 0.05, 0.05
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     13:17    3:48m  0.38s  0.38s -bash
    root     pts/1    192.168.74.1     20:30   46:14   0.05s  0.05s -bash

 

3、自定义grains

如果自带的grains不满足需求,可以自定义grains;自定义grains有两种方式实现:在server端写好后推到client,或者直接在client端编辑;

在server端定义如下:

#定义file_roots的base目录,并新建grains目录
 file_roots:
   base:
     - /srv/salt/base
[root@linux-node1 _grains]# pwd
/srv/salt/base/_grains

#定义grains,获取客户端的ulimit -n 的值
[root@linux-node1 _grains]# cat file.py   
import os
def file():
    grains={}
    file = os.popen('ulimit -n').read()
    grains['file']=file
    return grains

#将grains推送到需要的客户端
salt '*' saltutil.sync_all

#获取grains的值
[root@linux-node1 _grains]# salt '*' grains.get file
linux-node2-computer:
    1024
linux-node1.oldboyedu.com:
    8192

 

在客户端编辑如下:

将minion的配置文件/etc/salt/minion如下的注释去掉

grains:
  roles:
    - webserver
    - memcache

然后重启minion,测试:

[root@linux-node1 ~]# salt -G 'roles:memcache'  cmd.run 'echo hehe'        
linux-node1.example.com:
    hehe

  

当然了,自定义grains一般不写在配置文件中,放在文件/etc/salt/grains 中:

[root@linux-node1 ~]# cat /etc/salt/grains 
web: nginx    #这里的web,即key不能喝系统已经有的grains的key有重复,否则会出问题啦

测试一下:

[root@linux-node1 ~]# systemctl restart salt-minion    #每次自定义grains,都必须重启minion,否则不生效啊
[root@linux-node1 ~]# salt '*' grains.item roles
linux-node1.example.com:
    ----------
    roles:
        - webserver
        - memcache
linux-node2.example.com:
    ----------
    roles:
[root@linux-node1 ~]# salt '*' grains.item web
linux-node2.example.com:
    ----------
    web:
linux-node1.example.com:
    ----------
    web:
        nginx
[root@linux-node1 ~]# 
[root@linux-node1 ~]# salt -G 'web:nginx' cmd.run 'w'
linux-node1.example.com:
     21:42:33 up  6:02,  1 user,  load average: 0.25, 0.19, 0.11
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     20:30    1.00s  0.76s  0.34s /usr/bin/python /usr/bin/salt -G web:nginx cmd.run w

 

另外,grains 在minion端还可以写在default_include: minion.d/*.conf 下面

[root@linux-node2 minion.d]# cat charles.conf 
grains:
  FD: 2
  charles: 5
  cpis:
    - a
    - b
  charles.net:
    ++++++++++++++++++++++++++++

    +++++++++++++++++++=========
 
    ***************************


#测试结果
[root@linux-node1 _grains]# salt 'linux-node2-computer' grains.items
linux-node2-computer:
    ----------
    FD:
        2
    SSDs:
    biosreleasedate:
        06/02/2011
    biosversion:
        6.00
    charles:
        5
    charles.net:
        ++++++++++++++++++++++++++++
        +++++++++++++++++++=========
        ***************************
    cpis:
        - a
        - b

 

3、在top file中使用grains

匹配web: nginx的机器,执行apache.sls

[root@linux-node1 salt]# pwd
/srv/salt
[root@linux-node1 salt]# cat top.sls 
base:
  'web:nginx':
    - match: grain
    - apache

 

指定结果如下:

salt '*' state.highstate 

[root@linux-node1 salt]# salt '*' state.highstate
linux-node2.example.com:
----------
          ID: states
    Function: no.None
      Result: False
     Comment: No Top file or external nodes data matches found.
     Started: 
    Duration: 
     Changes:   

Summary
------------
Succeeded: 0
Failed:    1
------------
Total states run:     1
linux-node1.example.com:
----------
          ID: apache-install
    Function: pkg.installed
        Name: httpd
      Result: True
     Comment: Package httpd is already installed.
     Started: 21:51:23.629420
    Duration: 1910.371 ms
     Changes:   
----------
          ID: apache-install
    Function: pkg.installed
        Name: httpd-devel
      Result: True
     Comment: Package httpd-devel is already installed.
     Started: 21:51:25.539976
    Duration: 0.369 ms
     Changes:   
----------
          ID: apache-service
    Function: service.running
        Name: httpd
      Result: True
     Comment: Service httpd is already enabled, and is in the desired state
     Started: 21:51:25.540793
    Duration: 647.157 ms
     Changes:   

Summary
------------
Succeeded: 3
Failed:    0
------------
Total states run:     3
ERROR: Minions returned with non-zero exit code
[root@linux-node1 salt]# 

 

四、数据系统 pillar(给minion指定其想要的数据)

Grains:是部署在minion端的,支持静态数据,minion启动的时候采集,也可以使用saltutil.sync_grains进行刷新;应用比如:存储minion基本数据,比如用于匹配minion,自身数据可以用来做资产管理等;

Pillar:部署在master端,支持动态数据,在master端定义,指定给对应的minion,可以使用saltutil.reflesh_pillar刷新;应用举例:存储master指定的数据,只有指定的minion可以看到,用于敏感数据保存;

 

1、pillar默认是没有的内容的,需要在/etc/salt/master下面设置,将参数pillar_opts: True,然后重启mater就有啦

东西很多,然而并没有什么用~~,那就关掉吧

linux-node2.example.com:
    ----------
    master:
        ----------
        __role:
            master
        auth_mode:
            1
        auto_accept:
            False
        cache_sreqs:
            True
        cachedir:
            /var/cache/salt/master
        cli_summary:
            False
        client_acl:
            ----------
        client_acl_blacklist:
            ----------
        cluster_masters:
        cluster_mode:
            paranoid
        con_cache:
            False
        conf_file:
            /etc/salt/master
        config_dir:
            /etc/salt
        cython_enable:
            False
        daemon:
            False
        default_include:
            master.d/*.conf
        enable_gpu_grains:
            False
        enforce_mine_cache:
            False
        enumerate_proxy_minions:
            False
        environment:
            None
        event_return:
        event_return_blacklist:
        event_return_queue:
            0
        event_return_whitelist:
        ext_job_cache:
        ext_pillar:
        extension_modules:
            /var/cache/salt/extmods
        external_auth:
            ----------
        failhard:
            False
        file_buffer_size:
            1048576
        file_client:
            local
        file_ignore_glob:
            None
        file_ignore_regex:
            None
        file_recv:
            False
        file_recv_max_size:
            100
        file_roots:
            ----------
            base:
                - /srv/salt
        fileserver_backend:
            - roots
        fileserver_followsymlinks:
            True
        fileserver_ignoresymlinks:
            False
        fileserver_limit_traversal:
            False
        gather_job_timeout:
            10
        gitfs_base:
            master
        gitfs_env_blacklist:
        gitfs_env_whitelist:
        gitfs_insecure_auth:
            False
        gitfs_mountpoint:
        gitfs_passphrase:
        gitfs_password:
        gitfs_privkey:
        gitfs_pubkey:
        gitfs_remotes:
        gitfs_root:
        gitfs_user:
        hash_type:
            md5
        hgfs_base:
            default
        hgfs_branch_method:
            branches
        hgfs_env_blacklist:
        hgfs_env_whitelist:
        hgfs_mountpoint:
        hgfs_remotes:
        hgfs_root:
        id:
            linux-node2.example.com
        interface:
            0.0.0.0
        ioflo_console_logdir:
        ioflo_period:
            0.01
        ioflo_realtime:
            True
        ioflo_verbose:
            0
        ipv6:
            False
        jinja_lstrip_blocks:
            False
        jinja_trim_blocks:
            False
        job_cache:
            True
        keep_jobs:
            24
        key_logfile:
            /var/log/salt/key
        keysize:
            2048
        log_datefmt:
            %H:%M:%S
        log_datefmt_logfile:
            %Y-%m-%d %H:%M:%S
        log_file:
            /var/log/salt/master
        log_fmt_console:
            [%(levelname)-8s] %(message)s
        log_fmt_logfile:
            %(asctime)s,%(msecs)03.0f [%(name)-17s][%(levelname)-8s][%(process)d] %(message)s
        log_granular_levels:
            ----------
        log_level:
            warning
        loop_interval:
            60
        maintenance_floscript:
            /usr/lib/python2.7/site-packages/salt/daemons/flo/maint.flo
        master_floscript:
            /usr/lib/python2.7/site-packages/salt/daemons/flo/master.flo
        master_job_cache:
            local_cache
        master_pubkey_signature:
            master_pubkey_signature
        master_roots:
            ----------
            base:
                - /srv/salt-master
        master_sign_key_name:
            master_sign
        master_sign_pubkey:
            False
        master_tops:
            ----------
        master_use_pubkey_signature:
            False
        max_event_size:
            1048576
        max_minions:
            0
        max_open_files:
            100000
        minion_data_cache:
            True
        minionfs_blacklist:
        minionfs_env:
            base
        minionfs_mountpoint:
        minionfs_whitelist:
        nodegroups:
            ----------
        open_mode:
            False
        order_masters:
            False
        outputter_dirs:
        peer:
            ----------
        permissive_pki_access:
            False
        pidfile:
            /var/run/salt-master.pid
        pillar_opts:
            True
        pillar_roots:
            ----------
            base:
                - /srv/pillar
        pillar_safe_render_error:
            True
        pillar_source_merging_strategy:
            smart
        pillar_version:
            2
        pillarenv:
            None
        ping_on_rotate:
            False
        pki_dir:
            /etc/salt/pki/master
        preserve_minion_cache:
            False
        pub_hwm:
            1000
        publish_port:
            4505
        publish_session:
            86400
        queue_dirs:
        raet_alt_port:
            4511
        raet_clear_remotes:
            False
        raet_main:
            True
        raet_mutable:
            False
        raet_port:
            4506
        range_server:
            range:80
        reactor:
        reactor_refresh_interval:
            60
        reactor_worker_hwm:
            10000
        reactor_worker_threads:
            10
        renderer:
            yaml_jinja
        ret_port:
            4506
        root_dir:
            /
        rotate_aes_key:
            True
        runner_dirs:
        saltversion:
            2015.5.10
        search:
        search_index_interval:
            3600
        serial:
            msgpack
        show_jid:
            False
        show_timeout:
            True
        sign_pub_messages:
            False
        sock_dir:
            /var/run/salt/master
        sqlite_queue_dir:
            /var/cache/salt/master/queues
        ssh_passwd:
        ssh_port:
            22
        ssh_scan_ports:
            22
        ssh_scan_timeout:
            0.01
        ssh_sudo:
            False
        ssh_timeout:
            60
        ssh_user:
            root
        state_aggregate:
            False
        state_auto_order:
            True
        state_events:
            False
        state_output:
            full
        state_top:
            salt://top.sls
        state_top_saltenv:
            None
        state_verbose:
            True
        sudo_acl:
            False
        svnfs_branches:
            branches
        svnfs_env_blacklist:
        svnfs_env_whitelist:
        svnfs_mountpoint:
        svnfs_remotes:
        svnfs_root:
        svnfs_tags:
            tags
        svnfs_trunk:
            trunk
        syndic_dir:
            /var/cache/salt/master/syndics
        syndic_event_forward_timeout:
            0.5
        syndic_jid_forward_cache_hwm:
            100
        syndic_master:
        syndic_max_event_process_time:
            0.5
        syndic_wait:
            5
        timeout:
            5
        token_dir:
            /var/cache/salt/master/tokens
        token_expire:
            43200
        transport:
            zeromq
        user:
            root
        verify_env:
            True
        win_gitrepos:
            - https://github.com/saltstack/salt-winrepo.git
        win_repo:
            /srv/salt/win/repo
        win_repo_mastercachefile:
            /srv/salt/win/repo/winrepo.p
        worker_floscript:
            /usr/lib/python2.7/site-packages/salt/daemons/flo/worker.flo
        worker_threads:
            5
        zmq_filtering:
            False

 

pillar的用途:a针对敏感数据,比如对数据加密,只能是指定的minion看到;b变量的差异性;

 

2、使用自定义的pillar

开启pillar的sls,需要在master的配置文件中去掉如下的注释;

vim /etc/salt/master
pillar_roots:
  base:
    - /srv/pillar

创建pillar的目录和top file

[root@linux-node1 base]# pwd
/srv/pillar/base
[root@linux-node1 base]# ls
sc.sls  top.sls  zabbix
[root@linux-node1 base]# pwd
/srv/pillar/base
[root@linux-node1 base]# cat top.sls 
base:
  '*':
    - zabbix.agent                    #zabbix 下的agent.sls
  'linux-node2-computer':
    - sc                                     #sc.sls


#sc.sls
[root@linux-node1 base]# cat sc.sls 
cange: 1
charles.net: 2
DF: 22222
12_1T: cache_dir coss /data/cache1/coss 6000 max
    
       cache_dir aufs /data/cach1 20000 128 128 min
[root@linux-node1 base]# cat sc.sls 
cange: 1
charles.net: 2
DF: 22222
12_1T: cache_dir coss /data/cache1/coss 6000 max    #两行,必须空一行
    
       cache_dir aufs /data/cach1 20000 128 128 min


#测试
[root@linux-node1 base]# salt '*' pillar.data
linux-node2-computer:
    ----------
    12_1T:
        cache_dir coss /data/cache1/coss 6000 max
        cache_dir aufs /data/cach1 20000 128 128 min
    DF:
        22222
    cange:
        1
    charles.net:
        2
    zabbix-agent:
        ----------
        Zabbix_Server:
            192.168.74.20
linux-node1.oldboyedu.com:
    ----------
    zabbix-agent:
        ----------
        Zabbix_Server:
            192.168.74.20

 

 

在pillar的base目录下创建apache.sls文件,这里我们使用jinjia模板:

[root@22-57 pillar]# pwd
/srv/pillar
[root@22-57 pillar]# cat apache.sls 
{% if grains['os'] == 'CentOS' %}
apache: httpd
{% elif grains['os'] == 'Debian'%}
apache: apche2
{% endif %}

 

在top file中指定哪些minion可以接收到该pillar的数据:

[root@linux-node1 ~]# cd /srv/pillar/
[root@linux-node1 pillar]# cat top.sls 
base:
  '*':
    - apache

执行如下:

[root@22-57 pillar]# salt '*' pillar.items
172.16.22.35:
    ----------
    apache:
        httpd
172.16.22.57:
    ----------
    apache:
        httpd

 

 

当然pillar也用来匹配minion,前提是需要先刷新

刷新pillar,并执行pillar的sls;

[root@22-57 ~]# salt '*' saltutil.refresh_pillar
172.16.22.57:
    True
172.16.22.35:
    True
[root@22-57 ~]# salt -I 'apache:httpd' test.ping    #使用pillar匹配
172.16.22.35:
    True
172.16.22.57:
    True

 

3、grains和pillar的使用

#定义base 环境的top file
[root@linux-node1 base]# pwd
/srv/salt/base
[root@linux-node1 base]# cat top.sls
base:
  'linux-node2-computer':
    - test.test    #执行test目录下面的test.sls文件

#test.sls
[root@linux-node1 test]# cat test.sls 
/tmp/squid.conf:
  file.managed:
    - source: salt://test/squid.conf.jinjia     #引入jinja文件
    - template: jinja


#jinja文件
visible_host {{ grains['fqdn'] }}
{{ pillar['12_1T'] }}
{{ pillar['DF'] }}

test {{ grains['charles'] }}


#执行
[root@linux-node1 test]# salt '*' state.highstate 
linux-node1.oldboyedu.com:
----------
          ID: states
    Function: no.None
      Result: False
     Comment: No Top file or external nodes data matches found.
     Changes:   

Summary for linux-node1.oldboyedu.com
------------
Succeeded: 0
Failed:    1
------------
Total states run:     1
Total run time:   0.000 ms
linux-node2-computer:
----------
          ID: /tmp/squid.conf
    Function: file.managed
      Result: True
     Comment: File /tmp/squid.conf is in the correct state
     Started: 11:58:26.578232
    Duration: 38.022 ms
     Changes:   

Summary for linux-node2-computer
------------
Succeeded: 1
Failed:    0
------------
Total states run:     1
Total run time:  38.022 ms


#执行结果
[root@linux-node2 salt]# cat /tmp/squid.conf 
visible_host linux-node2.openstack.com
cache_dir coss /data/cache1/coss 6000 max
cache_dir aufs /data/cach1 20000 128 128 min
22222

test 5

  

五、远程执行详解

1、目标

a、使用通配符

[root@linux-node1 ~]# salt 'linux-node?.example.com' cmd.run 'w'
linux-node1.example.com:
     21:22:28 up  5:27,  2 users,  load average: 0.15, 0.17, 0.11
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    3:49m  0.13s  0.13s -bash
    root     pts/1    192.168.74.1     21:18    4.00s  0.54s  0.35s /usr/bin/python /usr/bin/salt linux-node?.example.com cmd.run w
linux-node2.example.com:
     21:22:28 up  5:27,  2 users,  load average: 0.02, 0.04, 0.05
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    5:51m  0.22s  0.22s -bash
    root     pts/1    192.168.74.1     21:18   28.00s  0.06s  0.06s -bash
[root@linux-node1 ~]# salt 'linux-node[1,2].example.com' cmd.run 'w' 
linux-node2.example.com:
     21:24:32 up  5:29,  2 users,  load average: 0.07, 0.06, 0.05
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    5:53m  0.22s  0.22s -bash
    root     pts/1    192.168.74.1     21:18    2:32   0.06s  0.06s -bash
linux-node1.example.com:
     21:24:32 up  5:29,  2 users,  load average: 0.09, 0.14, 0.11
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    3:51m  0.13s  0.13s -bash
    root     pts/1    192.168.74.1     21:18    0.00s  0.56s  0.36s /usr/bin/python /usr/bin/salt linux-node[1,2].example.com cmd.run w
[root@linux-node1 ~]# salt 'linux-node[1-2].example.com' cmd.run 'w'  
linux-node1.example.com:
     21:24:39 up  5:29,  2 users,  load average: 0.09, 0.14, 0.11
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    3:51m  0.13s  0.13s -bash
    root     pts/1    192.168.74.1     21:18    7.00s  0.55s  0.34s /usr/bin/python /usr/bin/salt linux-node[1-2].example.com cmd.run w
linux-node2.example.com:
     21:24:39 up  5:29,  2 users,  load average: 0.07, 0.06, 0.05
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.74.1     14:17    5:53m  0.22s  0.22s -bash
    root     pts/1    192.168.74.1     21:18    2:39   0.06s  0.06s -bash

b、正则表达式

[root@linux-node1 ~]# salt -E 'linux-node(1|2).example.com' test.ping    #正则表达式使用-E
linux-node1.example.com:
    True
linux-node2.example.com:
    True
[root@linux-node1 ~]# salt -L 'linux-node1.example.com,linux-node2.example.com' test.ping    #list
linux-node1.example.com:
    True
linux-node2.example.com:
    True  

c、ip地址

[root@linux-node1 ~]# salt -S '192.168.74.20' test.ping
linux-node1.example.com:
    True
[root@linux-node1 ~]# salt -S '192.168.74.0/24' test.ping  
linux-node1.example.com:
    True
linux-node2.example.com:
    True

  

多种匹配条件

[root@linux-node1 ~]# salt -C 'S@192.168.74.21 or G@web:nginx' test.ping   
linux-node1.example.com:
    True
linux-node2.example.com:
    True

  

注意:minion-id很重要

2、模块

a、service模块

[root@linux-node1 ~]# salt '*' service.available sshd     #判断sshd是否启动
linux-node1.example.com:
    True
linux-node2.example.com:
    True
salt '*' service.get_all    #获取所有的服务
[root@linux-node1 ~]# salt '*' service.missing sshd
linux-node2.example.com:
    False
linux-node1.example.com:
    False

 

重启服务 

[root@linux-node1 ~]# salt '*' service.reload httpd
linux-node2.example.com:
    True
linux-node1.example.com:
    True
[root@linux-node1 ~]# salt '*' service.status httpd
linux-node1.example.com:
    True
linux-node2.example.com:
    True
[root@linux-node1 ~]# salt '*' service.stop httpd  
linux-node1.example.com:
    True
linux-node2.example.com:
    True
[root@linux-node1 ~]# salt '*' service.start httpd
linux-node1.example.com:
    True
linux-node2.example.com:
    True

b、network

[root@linux-node1 ~]# salt '*' network.interface ens33 
linux-node1.example.com:
    |_
      ----------
      address:
          192.168.74.20
      broadcast:
          192.168.74.255
      label:
          ens33
      netmask:
          255.255.255.0
linux-node2.example.com:
    |_
      ----------
      address:
          192.168.74.21
      broadcast:
          192.168.74.255
      label:
          ens33
      netmask:
          255.255.255.0

 

这里介绍一下模块的ACL:

client_acl:
  oldboy:
    - test.ping      #只可以指定test.ping 和Network模块
    - network.*
  user01:
    - linux-node1*:      #支队node1可以执行test.ping
      - test.ping

    同时需要赋予普通用户对文件操作的权限

chmod 755 /var/cache/salt/ /var/cache/salt/master/ /var/cache/salt/master/jobs/  /var/run/salt /var/run/salt/master/

测试:

[user01@linux-node1 ~]$ salt 'linux-node1*' test.ping
linux-node1.example.com:
    True
[user01@linux-node1 ~]$ salt '*' test.ping           
Failed to authenticate! This is most likely because this user is not permitted to execute commands, but there is a small possibility that a disk error occurred (check disk/inode usage).

 

 salt还可以设置黑名单

#
#client_acl_blacklist:
#  users:
#    - root
#    - '^(?!sudo_).*$'   #  all non sudo users
#  modules:
#    - cmd

 

3、返回

 

创建salt的库和表:

mysql> CREATE DATABASE`salt`
    -> DEFAULT CHARACTER SET utf8
    -> DEFAULT COLLATE utf8_general_ci;
Query OK, 1 row affected (0.03 sec)

mysql> use salt;
Database changed

DROP TABLE IF EXISTS `jids`;
CREATE TABLE `jids` (
  `jid` varchar(255) NOT NULL,
  `load` mediumtext NOT NULL,
  UNIQUE KEY `jid` (`jid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX jid ON jids(jid) USING BTREE;

--
-- Table structure for table `salt_returns`
--

DROP TABLE IF EXISTS `salt_returns`;
CREATE TABLE `salt_returns` (
  `fun` varchar(50) NOT NULL,
  `jid` varchar(255) NOT NULL,
  `return` mediumtext NOT NULL,
  `id` varchar(255) NOT NULL,
  `success` varchar(10) NOT NULL,
  `full_ret` mediumtext NOT NULL,
  `alter_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  KEY `id` (`id`),
  KEY `jid` (`jid`),
  KEY `fun` (`fun`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Table structure for table `salt_events`
--

DROP TABLE IF EXISTS `salt_events`;
CREATE TABLE `salt_events` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`tag` varchar(255) NOT NULL,
`data` mediumtext NOT NULL,
`alter_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`master_id` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `tag` (`tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> show tables;
+----------------+
| Tables_in_salt |
+----------------+
| jids           |
| salt_events    |
| salt_returns   |
+----------------+
3 rows in set (0.00 sec)

mysql> 
mysql> grant all on salt.* to salt@'192.168.74.0/255.255.255.0' identified by 'salt';
Query OK, 0 rows affected (0.00 sec)

 

所有的minion和master都必须安装MySQL-python的包;

 

在master和minion的配置文件中添加如下配置:

return: mysql   #如果没有该参数,需要加--return参数

mysql.host: '192.168.74.20'
mysql.user: 'salt'
mysql.pass: 'salt'
mysql.db: 'salt'
mysql.port: 3306

 

重启配置文件之后

[root@linux-node1 ~]# salt '*' cmd.run 'uptime'
linux-node2.example.com:
     22:09:15 up 10:21,  1 user,  load average: 0.07, 0.07, 0.07
linux-node1.example.com:
     22:09:15 up 10:21,  2 users,  load average: 0.16, 0.15, 0.11
[root@linux-node1 ~]# salt '*' cmd.run 'df -h' --return mysql
linux-node1.example.com:
    Filesystem               Size  Used Avail Use% Mounted on
    /dev/mapper/centos-root   18G   12G  5.6G  69% /
    devtmpfs                 485M     0  485M   0% /dev
    tmpfs                    495M   16K  495M   1% /dev/shm
    tmpfs                    495M   14M  482M   3% /run
    tmpfs                    495M     0  495M   0% /sys/fs/cgroup
    /dev/sda1                497M  125M  373M  26% /boot
    tmpfs                     99M     0   99M   0% /run/user/0
    /dev/sr0                 4.1G  4.1G     0 100% /mnt
linux-node2.example.com:
    Filesystem               Size  Used Avail Use% Mounted on
    /dev/mapper/centos-root   18G   12G  5.6G  68% /
    devtmpfs                 215M     0  215M   0% /dev
    tmpfs                    225M   16K  225M   1% /dev/shm
    tmpfs                    225M   13M  212M   6% /run
    tmpfs                    225M     0  225M   0% /sys/fs/cgroup
    /dev/sda1                497M  125M  373M  26% /boot
    tmpfs                     45M     0   45M   0% /run/user/0
    /dev/sr0                 4.1G  4.1G     0 100% /mnt

 

返回的数据会存放在salt_returns表中

 

或者使用job_cache,将接受到的数据写入mysql中

master_job_cache: mysql

mysql.host: '192.168.74.20'
mysql.user: 'salt'
mysql.pass: 'salt'
mysql.db: 'salt'
mysql.port: 3306

 

 

 

 六、salt配置管理

salt的配置管理基于远程执行的

在master的配置文件中指定top file文件位置

file_roots:
  base:
    - /srv/salt/base
  test:
    - /srv/salt/test
  prod:
    - /srv/salt/prod
[root@22-57 ~]# mkdir /srv/salt/{base,test,prod}
[root@22-57 ~]# cd /srv/salt/
[root@22-57 salt]# mv top.sls apache.sls base/
[root@22-57 salt]# ll
总用量 0
drwxr-xr-x 2 root root 37 12月 12 23:08 base
drwxr-xr-x 2 root root  6 12月 12 23:07 prod
drwxr-xr-x 2 root root  6 12月 12 23:07 test
[root@22-57 salt]# pwd
/srv/salt

 

练习:salt文件管理

[root@22-57 base]# cat dns.sls 
/var/log/secure:
  file.managed:
    - souce: salt://files/secure
    - user: root
    - group: root
    - mode: 777

有两种执行方式:

salt '*' state.sls dns

或者定义top file,

[root@22-57 base]# cat top.sls 
base:
  '*':
    - dns

salt '*' state.highstate执行

 

六、saltstak配置管理  -yaml和jinjia

配置管理三步分:1、系统初始化。2、功能模块。3、业务模块。

1、yaml语法规则

规则一:缩进,每一个缩进级别使用两个空格组成。

规则二:冒号,每一个冒号后面都有一个空格,处理以冒号结尾和使用冒号表示路径之外。

规则三:短横线,想要表示列表项,使用短横线加空格。

2、YAML和Jinjia

步骤:系统初始化-->功能模块-->业务模块

YAML的语法规则:

a、使用两个空格表示层级关系;  
b、冒号,每个冒号之后都有一个空格,除了以冒号结尾的;
c、短横线(列表),每一个短横线后面都需要有一个空格;

 

Jinjia模板:这里使用例子说明,还是之前的DNS文件

		[root@linux-node1 base]# cat dns.sls 
				/etc/resolv.conf:
				  file.managed:
					- source: salt://files/resolv.conf
					- user: root
					- group: root
					- mode: 644
					- template: jinja      #使用jinj模板
					- defaults: 
					  DNS_SERVER: 8.8.8.8   #定义模板变量
		[root@linux-node1 base]# cat files/resolv.conf 
				#dns server
				nameserver {{ DNS_SERVER }}    #引用模板变量
		
		[root@linux-node1 base]# cat files/resolv.conf 
				#dns server
				# {{ grains['fqdn_ip4']}}      #也可以在Jinja模板中使用grains
				nameserver {{ DNS_SERVER }}
				
				#jinja模板中也可以加执行模块,加pillar

 

3、系统初始化

将系统初始化的sls全部放在/srv/salt/base/init下面,使用top.sls进行调研

[root@22-57 base]# tree 
.
├── apache.sls
├── init
│   ├── audit.sls
│   ├── dns.sls
│   ├── env_init.sls
│   ├── files
│   │   ├── resolv.conf
│   │   └── secure
│   ├── history.sls
│   └── sysctl.sls
└── top.sls

 

[root@22-57 base]# cat top.sls 
base:
  '*':
    - init.env_init

[root@22-57 init]# cat env_init.sls 
include:
  - init.dns
  - init.history
  - init.audit
  - init.sysct

  

初始化文件全部写在init目录下

增加环境变量,让history文件记录时间戳
[root@22-57 init]# cat history.sls 
/etc/profile:
  file.append:
    - text:
      - export HISTTIMEFORMAT="%F %T `whoami` "


日志审计:将操作命令全部记录在message文件中
[root@22-57 init]# cat audit.sls 
/etc/bashrc:
  file.append:
    - text:
      - export PROMPT_COMMAND='{ msg=$(history 1 | { read x y; echo $y;});logger "[euid=$(whoami)]":$(who am i):[`pwd`]"$msg";}'


调整内核参数:不使用交换分区,重新规划端口范围,设定可以打开的文件数量
[root@22-57 init]# cat sysctl.sls 
vm.swappiness:
  sysctl.present:
    - value: 0


net.ipv4.ip_local_port_range:
  sysctl.present:
    - value: 10000 65000

fs.file-max:
  sysctl.present:
    - value: 100000

 

现在基础环境下有了这几个sls:

[root@linux-node1 init]# ls -l
	total 20
	-rw-r--r--. 1 root root 168 Jan 26 18:19 audit.sls
	-rw-r--r--. 1 root root 194 Jan 25 22:27 dns.sls
	drwxr-xr-x. 2 root root  24 Jan 25 22:27 files
	-rw-r--r--. 1 root root  88 Jan 26 18:09 history.sls
	-rw-r--r--. 1 root root 175 Jan 26 18:35 sysctl.sls

 

如果将上面的这些SLS放入TOP file中执行的话,top file的内容会很多,所以定义一个sls,将所有的sls include进去:

[root@linux-node1 init]# cat env_init.sls     #init.表示文件的路径,所有的sls是从base目录开始查找的
	include:
	  - init.dns
	  - init.history
	  - init.audit
	  - init.sysctl

 

最后调用:

[root@linux-node1 base]# cat top.sls     #最后在top file 中引用
	base:
	  '*':
		- init.env_init 	

[root@linux-node1 ~]# salt '*' state.highstate test=True     #测试
[root@linux-node1 ~]# salt '*' state.highstate

 

ps:涉及到的系统知识

export PROMPT_COMMAND='{msg=$(history 1 | {read x y;echo $y;});logger "[euid=$(whoami)]";$(who am i):[`pwd`]"$msg";}'    #将操作日志加到message中


cat /proc/sys/net/ipv4/ip_local_port_range     #端口范围

 
cat /proc/sys/fs/file-max    #打开文件描述符数



salt '*' state.highstate test=True    #测试sls,看执行了哪些操作

  

4、业务引用haproxy

[root@22-57 cluster]# cat haproxy-outside.sls 
include:
  - haproxy.install

haproxy-service:
  file.managed:
    - name: /etc/haproxy/haproxy.cfg
    - source: salt://cluster/files/haproxy-outside.cfg
    - user: root
    - group: root
    - mode: 644
  service.running:
    - name: haproxy
    - enable: True
    - reload: True
    - require:
      - cmd: haproxy-init
    - watch:                 #配置文件改变,自动reload
      - file: haproxy-service
[root@22-57 cluster]# pwd
/srv/salt/prod/cluste

 

[root@22-57 base]# cat top.sls 
base:
  '*':
    - init.env_init

prod:
  '172.16.22.57':
    - cluster.haproxy-outside
  '172.16.22.35':
    - cluster.haproxy-outside
[root@22-57 base]# pwd
/srv/salt/bas

 

haproxy的配置文件如下:

[root@22-57 files]# cat haproxy-outside.cfg 
global
maxconn 100000
chroot /usr/local/haproxy
uid 99
gid 99
daemon
nbproc 1
pidfile /usr/local/haproxy/logs/haproxy.pid
log 127.0.0.1 local3 info

defaults
option http-keep-alive
maxconn 100000
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms

listen stats
mode http
bind 0.0.0.0:8888
stats enable
stats uri    /haproxy-status
stats auth   haproxy:saltstack

frontend frontend_www_example_com
bind 172.16.22.50:80
mode http
option httplog
log global
    default_bankend backend_www_example_com

backend backend_www_example_com
option forwardfor header X-REAL-IP
option httpchk HEAD / HTTP/1.0
balance source
server web-node1 172.16.22.57:8080 check inter 2000 rise 30 fall 15
server web-node2 172.16.22.35:8080 check inter 2000 rise 30 fall 15

 

http://192.168.74.20:8888/haproxy-status
解决haproxy 403问题:修改/var/www/html/index.html内容
http://192.168.74.21:8080/

需要的salt的文件目录如下:

[root@linux-node1 prod]# pwd
/srv/salt/prod
[root@linux-node1 prod]# ls -l
total 0
drwxr-xr-x. 3 root root 81 Jan 26 22:40 cluster #业务模块
drwxr-xr-x. 3 root root 36 Jan 26 23:01 haproxy #haproxy的功能模块
drwxr-xr-x. 2 root root 25 Jan 26 22:52 pkg #所有包的安装

 

5、配置管理keepalived

keepalived是vrrp协议的实现;

[root@linux-node1 keepalived]# cat install.sls   #安装
		keepalived-install:
		  file.managed:
			- name: /usr/local/src/keepalived-1.2.17.tar.gz
			- source: salt://keepalived/files/keepalived-1.2.17.tar.gz
			- mode: 755
			- user: root
			- group: root
		  cmd.run:
			- name: cd /usr/local/src && tar zxf keepalived-1.2.17.tar.gz && cd keepalived-1.2.17 && ./configure --prefix=/usr/local/keepalived --disable-fwmark && make && make install
			- unless: test -d /usr/local/keepalived
			- require:
			  - file: keepalived-install

		/etc/sysconfig/keepalived:
		  file.managed:
			- source: salt://keepalived/files/keepalived.sysconfig
			- mode: 644
			- user: root
			- group: root

		/etc/init.d/keepalived:
		  file.managed:
			- source: salt://keepalived/files/keepalived.init
			- mode: 755
			- user: root
			- group: root

		keepalived-init:
		  cmd.run:
			- name: chkconfig --add keepalived
			- unless: chkconfig --list | grep keepalived
			- require:
			  - file: /etc/init.d/keepalived

		/etc/keepalived:
		  file.directory:
			- user: root
			- group: root
			
			
[root@linux-node1 ~]# salt '*' state.sls keepalived.install env=prod    #执行

 

6、业务引用keepalived

[root@22-57 init]# cat zabbix_agent.sls 
zabbix-agent-install:
  pkg.installed:
    - name: zabbix-agent

  file.managed:
    - name: /etc/zabbix/zabbix_agent.d.conf
    - source: salt://init/files/zabbix_agentd.conf
    - template: jiajia
    - defaults:
      Server: {{ pillar['zabbix-agent']['Zabbix_Server'] }}    #使用自定义的pillar
    - require:
      - pkg: zabbix-agent-install

  service.running:
    - name: zabbix-agent
    - enable: True
    - watch:
      - pkg: zabbix-agent-install
      - file: zabbix-agent-install
[root@22-57 init]# pwd
/srv/salt/base/init

 

[root@linux-node1 zabbix]# cat agent.sls 
zabbix-agent:
  Zabbix_Server: 192.168.74.20
[root@linux-node1 zabbix]# pwd
/srv/pillar/base/zabbix

  

[root@linux-node1 base]# cat top.sls 
base:
  '*':
    - zabbix.agent
[root@linux-node1 base]# pwd
/srv/pillar/base

 

[root@linux-node1 base]# salt '*' pillar.items
linux-node2.example.com:
    ----------
    zabbix-agent:
        ----------
        Zabbix_Server:
            192.168.74.20
linux-node1.example.com:
        ----------
    zabbix-agent:
        ----------
        Zabbix_Server:
            192.168.74.20 

需要注意的是,在master的配置文件中,需要将pillar_roots的目录设置为/srv/pillar/base;

 

将zabbix-agent加载的基础服务中

[root@22-57 init]# cat env_init.sls 
include:
  - init.dns
  - init.history
  - init.audit
  - init.sysctl
  - init.zabbix_agent

  

详细代码请参考:

https://github.com/unixhot/saltbook-code 

 

zabbix监控mysal的插件:

https://www.percona.com/software/database-tools/percona-monitoring-and-management 

  

七、saltstack架构扩展

生产应用不要使用root用户;
online/offline;
最佳实践不是删掉,而是mv到offline下面;
作业:mysql安装,实现自动化安装

salt-minion重新启动之后,需要等待一会时间再使用;

共享配置文件建议放在版本管理中;

 

1、

安装python-setproctitle可以查看python程序的进程名;

重启salt-master之后,就可以查看master的进程名啦!

一般情况下,salt如果由于网络延迟造成返回失败的话,可以将timeout的时间定的长一点(在master的配置文件中设定);

 

当然,saltstak支持无master的情况,也就是在minion端本地执行salt的相关命令,需要首先在minion的配置文件中将file_client配置为remote,重启minion,

[root@22-57 ~]# salt-call --local test.ping
local:
    True

这样就可以通过salt-call执行啦!

主要是在没有master的情况下,安装master的时候使用,可以将master的相关配置文件(source),放置到公网上,使用http进行相关配置(source支持http),配置目录结构等;

 

 

 

minion当然支持多个mater,不建议使用;

 

2、syndic

必须运行在一个master上,并且在连接到另外一个master(比他更高级)。

有两台机器:172.16.22.35和172.16.22.57

 

首先在172.16.22.35上安装salt-master和salt-syndic,在172.16.22.57上安装salt-master;

在172.16.22.57上的master的配置文件中配置syndic的地址:  syndic_master: 172.16.22.57;

停止两台机器的minion,并将/etc/salt/pki/minion的内容清空,如果有minion_id文件,建议删除(必须停止minion后在删除密钥文件);

重启172.16.22.35上的master,启动salt-syndic,将minion端的配置文件的Server地址配置为172.16.22.35;

启动minion;

这样在172.16.22.57 上执行salt-key,可以看到172.16.22.35,这是master,不是minion,信任他吧!同时在172.16.22.35上可以看到35和57l两台机器,同样信任;这样,就可以在master上执行salt的命令了,实现了分布式!

[root@22-57 pki]# salt-key 
Accepted Keys:
172.16.22.35
Denied Keys:
Unaccepted Keys:
Rejected Keys:
[root@22-57 pki]# 
[root@22-57 pki]# 
[root@22-57 pki]# 
[root@22-57 pki]# salt '*' test.ping
172.16.22.57:
    True
172.16.22.35:
    True

  

优点:可以建立多层级的架构;   minion<-->syndic<--->master

缺点:需要保证syndic的file_roots需要和master一致,并且master不知道syndic有几个,master不知道minion归哪个syndic管理;

发送时,syndic二次下发给minion,收的时候,由syndic将接收的结果发送给master;

 

八、syndic二次开发

1、自定义grains

首先在master的file_roots的base目录下,创建自定义grains保存的目录,并创建一个grains的实例;

[root@22-57 _grains]# pwd
/srv/salt/base/_grains
[root@22-57 _grains]# cat my_grains.py 
#!/usr/bin/env python
def my_grains():
    '''
    My Custom Grains
    '''
    grains= {'hehe1':'haha1','hehe2':'haha2'}
    return grains

使用命令:salt '*' saltutil.sync_grains,就会把自定义的grains传到minion的缓存下面,缓存目录为:

[root@qiangqiang grains]# pwd
/var/cache/salt/minion/extmods/grains
[root@qiangqiang grains]# ls
my_grains.py  my_grains.pyc

 

这样就可以使用自定义的grains了

[root@22-57 _grains]# salt '*' grains.item hehe1
172.16.22.57:
    ----------
    hehe1:
        haha1
172.16.22.35:
    ----------
    hehe1:
        haha1
[root@22-57 _grains]# salt '*' grains.item hehe2
172.16.22.35:
    ----------
    hehe2:
        haha2
172.16.22.57:
    ----------
    hehe2:
        haha2

 

2、自定义模块

在base目录下创建modules的目录,并创建自定义的module

[root@22-57 _modules]# pwd
/srv/salt/base/_modules
[root@22-57 _modules]# ls
my_disk.py

salt的模块存放路径为:/usr/lib/python2.6/site-packages/salt/,salt的module支持调用其他的salt模块;

[root@22-57 _modules]# cat my_disk.py 
def list():
    cmd= 'df -h'
    ret = __salt__['cmd.run'](cmd)
    return ret

 

salt '*' saltutil.sync_modules将自定义的模块发送到minion端;

salt '*' my_disk.list  调用自定义的salt模块;

 

九、运维自动化

1、运维自动化的三个阶段

第一:标准化,工具化;

     运维标准化,操作工具化,变更流程化,标准化运维;

第二:web化:为了弱化流程,直接走下一步就可以了,减少因为执行顺序的原因导致认为失误; 

    操作web化,权限控制、统计分析、统一调度 、web化运维

第三:服务化,API化;

    DNS服务、负载均衡服务、监控服务、分布式缓存服务、分布式存储服务、CMDB;

2、自动化扩容加etcd部署;

job的添加,需要通过web进行添加;

job添加需要提交申请;

 

Zabbix监控-->Action-->创建一台虚拟机/Docker容器--->部署服务-->部署代码-->测试状态-->加入集群-->加入监控-->通知

 

etcd介绍:用于共享配置和服务发现

    a、简单:curl可以访问用户的API(HTTP+JSON);

    b、使用Raft保证数据的一直性;

 

思路:将haproxy的配置写在etcd中,然后只有salt.pillar.etcd_pillar,就可以动态获取到这个pillar,最后在Jinjia中使用for循环就搞定了;

安装etcd:

将etcd的安装文件放置在/usr/local/src下,解压
拷贝二进制文件到/usr/local/bin下;
[root@22-57 ~]# cd /usr/local/src/etcd-v2.2.1-linux-amd64
[root@22-57 etcd-v2.2.1-linux-amd64]# ls
Documentation  etcd  etcdctl  README-etcdctl.md  README.md
[root@22-57 etcd-v2.2.1-linux-amd64]# cp etcd etcdctl /usr/local/bin/

[root@22-57 ~]# etcd --version    #查看版本
etcd Version: 2.2.1
Git SHA: 75f8282
Go Version: go1.5.1
Go OS/Arch: linux/amd6

 

启动etcd:

[root@22-57 ~]# nohup etcd --name auto_scale --data-dir /data/etcd/ --listen-peer-urls 'http://172.16.22.57:2380,http://172.16.22.57:7001' --listen-client-urls 'http://172.16.22.57:2379,http://172.16.22.57:4001' --advertise-client-urls 'http://172.16.22.57:2379,http://172.16.22.57:4001' &
[root@22-57 ~]# netstat -ntlp|grep etcd
tcp        0      0 172.16.22.57:4001           0.0.0.0:*                   LISTEN      53941/etcd          
tcp        0      0 172.16.22.57:2379           0.0.0.0:*                   LISTEN      53941/etcd          
tcp        0      0 172.16.22.57:2380           0.0.0.0:*                   LISTEN      53941/etcd          
tcp        0      0 172.16.22.57:7001           0.0.0.0:*                   LISTEN      53941/etcd 

 

使用curl put数据

[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/message -XPUT -d value="Hello world" |python -m json.tool     #-d表示put的数据的内容
{
    "action": "set", 
    "node": {
        "createdIndex": 6, 
        "key": "/message", 
        "modifiedIndex": 6, 
        "value": "Hello world"
    }, 
    "prevNode": {
        "createdIndex": 5, 
        "key": "/message", 
        "modifiedIndex": 5, 
        "value": "Hello world"
    }
}

获取put的值:

[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/message |python -m json.tool
{
    "action": "get", 
    "node": {
        "createdIndex": 6, 
        "key": "/message", 
        "modifiedIndex": 6, 
        "value": "Hello world"
    }
}

 

删除值:

[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/message -XDELETE |python -m json.tool
{
    "action": "delete", 
    "node": {
        "createdIndex": 6, 
        "key": "/message", 
        "modifiedIndex": 7
    }, 
    "prevNode": {
        "createdIndex": 6, 
        "key": "/message", 
        "modifiedIndex": 6, 
        "value": "Hello world"
    }
}
[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/message |python -m json.tool
{
    "cause": "/message", 
    "errorCode": 100, 
    "index": 7, 
    "message": "Key not found"
}

  

put一条数据,设置5秒之后自动删除:

[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/ttl_use -XPUT -d value="Hello world 1" -d ttl=5 |python -m json.tool
{
    "action": "set", 
    "node": {
        "createdIndex": 10, 
        "expiration": "2016-12-18T02:57:52.420004181Z", 
        "key": "/ttl_use", 
        "modifiedIndex": 10, 
        "ttl": 5, 
        "value": "Hello world 1"
    }
}


#自动删除
[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/ttl_use |python -m json.tool
{
    "action": "get", 
    "node": {
        "createdIndex": 10, 
        "expiration": "2016-12-18T02:57:52.420004181Z", 
        "key": "/ttl_use", 
        "modifiedIndex": 10, 
        "ttl": 1,     #剩余时间
        "value": "Hello world 1"
    }
}
[root@22-57 ~]# curl -s http://172.16.22.57:2379/v2/keys/ttl_use |python -m json.tool
{
    "cause": "/ttl_use", 
    "errorCode": 100, 
    "index": 11, 
    "message": "Key not found"
}

 

这样,在haproxy中添加的配合到时也会清除;

 

3、基于etcd和salstack的自动化扩容

 

 首先编辑salt-master的配置文件,并重启;这样salt-master和etcd就可以通信了;可以通过salt '*' pillar.items就可以获取到设置的pillar了;

etcd_pillar_config:
  etcd.host: 172.16.22.57
  etcd.port: 4001

ext-pillar:
  - etcd: etcd_pillar_config root=/salt/haproxy/

 然后重启salt-master

使用pip 安装python-etcd

 

向etcd中添加key,value的数据,需要安装python-etcd的模块之后才可以获取pillar的值;

[root@linux-node1 ~]# curl -s http://192.168.74.20:2379/v2/keys/salt/haproxy/backend_www_oldboyedu_com/web-node1 -XPUT -d value="192.168.74.20:8080" |python -m json.tool
{
    "action": "set",
    "node": {
        "createdIndex": 20,
        "key": "/salt/haproxy/backend_www_oldboyedu_com/web-node1",
        "modifiedIndex": 20,
        "value": "192.168.74.20:8080"
    }
}    #创建了一个key,这个key在/salt/haproxy/backend_www_oldboyedu_com/下,key为web-node1,key的值为192.168.74.20:8080

 

	[root@linux-node1 ~]# salt '*' pillar.items
	linux-node1.example.com:
		----------
		backend_www_oldboyedu_com:
			----------
			web-node1:
				192.168.74.20:8080
		bankend_www_oldboyedu_com:
			----------
			web-node1:
				192.168.74.20:8080
		zabbix-agent:
			----------
			Zabbix_Server:
				192.168.74.20
	linux-node2.example.com:
		----------
		backend_www_oldboyedu_com:
			----------
			web-node1:
				192.168.74.20:8080
		bankend_www_oldboyedu_com:
			----------
			web-node1:
				192.168.74.20:8080
		zabbix-agent:
			----------
			Zabbix_Server:
				192.168.74.20

  

 

然后在haproxy的配置文件中替换最后的server的相关配置为jinja模板的数据:

	[root@linux-node1 files]# pwd
	/srv/salt/prod/cluster/files
	[root@linux-node1 files]# cat haproxy-outside.cfg 
	global
	maxconn 100000
	chroot /usr/local/haproxy
	uid 99  
	gid 99 
	daemon
	nbproc 1 
	pidfile /usr/local/haproxy/logs/haproxy.pid 
	log 127.0.0.1 local3 info

	defaults
	option http-keep-alive
	maxconn 100000
	mode http
	timeout connect 5000ms
	timeout client  50000ms
	timeout server 50000ms

	listen stats
	mode http
	bind 0.0.0.0:8888
	stats enable
	stats uri     /haproxy-status 
	stats auth    haproxy:saltstack

	frontend frontend_www_example_com
	bind 192.168.74.22:80
	mode http
	option httplog
	log global
		default_backend backend_www_example_com

	backend backend_www_example_com
	option forwardfor header X-REAL-IP
	option httpchk HEAD / HTTP/1.0
	balance roundrobin
	 
	{% for web,web_ip in pillar.backend_www_oldboyedu_com.iteritems() %}
	server {{ web }} {{ web_ip }} check inter 2000 rise 30 fall 15
	{% endfor %}
	
	
	[root@linux-node1 cluster]# cat haproxy-outside-keepalived.sls   #并且要告诉sls使用的是Jinja模板
	include:
	  - keepalived.install
	keepalived-server:
	  file.managed:
		- name: /etc/keepalived/keepalived.conf
		- source: salt://cluster/files/haproxy-outside-keepalived.conf
		- mode: 644
		- user: root
		- group: root
		- template: jinja
		{% if grains['fqdn'] == 'linux-node1.example.com' %}
		- ROUTEID: haproxy_ha
		- STATEID: MASTER
		- PRIORITYID: 150
		{% elif grains['fqdn'] == 'linux-node2.example.com' %}
		- ROUTEID: haproxy_ha
		- STATEID: BACKUP
		- PRIORITYID: 100
		{% endif %}
	  service.running:
		- name: keepalived
		- enable: True
		- watch:
		  - file: keepalived-server
		  

 

[root@22-57 cluster]# cat haproxy-outside.sls 
include:
  - haproxy.install

haproxy-service:
  file.managed:
    - name: /etc/haproxy/haproxy.cfg
    - source: salt://cluster/files/haproxy-outside.cfg
    - user: root
    - group: root
    - mode: 644
    - template: jinja    #一定要指名模板
  service.running:
    - name: haproxy
    - enable: True
    - reload: True
    - require:
      - cmd: haproxy-init
    - watch:
      - file: haproxy-service
[root@22-57 cluster]# pwd
/srv/salt/prod/cluster

  

这样就可以通过在etcd中添加数据,来增加haproxy的节点了;

 

curl -s http://192.168.74.20:2379/v2/keys/salt/haproxy/backend_www_oldboyedu_com/web-node2 -XPUT -d value="192.168.74.20:8080" |python -m json.tool
curl -s http://192.168.74.20:2379/v2/keys/salt/haproxy/backend_www_oldboyedu_com/web-node3 -XPUT -d value="192.168.74.20:8080" |python -m json.tool
curl -s http://192.168.74.20:2379/v2/keys/salt/haproxy/backend_www_oldboyedu_com/web-node4 -XPUT -d value="192.168.74.20:8080" |python -m json.tool

最后:salt '*' state.highstate

  

 

通过脚本实现如下:

[root@22-57 ~]# cat auth.sh 
#!/bin/sh

create_host(){
    echo "create host"
}

deploy_service(){
    salt '172.16.22.35' state.sls nginx.install env=prod
    ADD_HOST="172.16.22.35"
    ADD_HOST_PORT="8080"
}

delpoy_code(){
   echo "deploy code ok"
}

service_check(){
    STATUS=$(curl -s --head http://"$ADD_HOST":"$ADD_HOST_PORT"/ |grep '200 OK')
    if [ -n "$STATUS" ]
        echo "ok"
    else
        echo "not ok"
        exit
    fi

}

etcd_key(){
    curl "http://172.16.22.57:2379/v2/keys/salt/haproxy/backend_www_oldboyedu_com/web-node1 -XPUT -d value="${ADD_HOT}:${ADD_HOST_PORT}""

}

sync_state(){
    salt '*' '172.16.22.57' state.sls cluster.haproxy-outside env=prod
}

main(){
    create_host;
    deploy_service;
    deploy_code;
    etcd_key;
    sync_state;
}

main

  

# -*- coding: utf-8 -*-
'''
The static grains, these are the core, or built in grains.

When grains are loaded they are not loaded in the same way that modules are
loaded, grain functions are detected and executed, the functions MUST
return a dict which will be applied to the main grains dict. This module
will always be executed first, so that any grains loaded here in the core
module can be overwritten just by returning dict keys with the same value
as those returned here
'''

# Import python libs
from __future__ import absolute_import
import os
import json
import socket
import sys
import re
import platform
import logging
import locale
import uuid
from errno import EACCES, EPERM

__proxyenabled__ = ['*']
__FQDN__ = None

# Extend the default list of supported distros. This will be used for the
# /etc/DISTRO-release checking that is part of linux_distribution()
from platform import _supported_dists
_supported_dists += ('arch', 'mageia', 'meego', 'vmware', 'bluewhite64',
'slamd64', 'ovs', 'system', 'mint', 'oracle', 'void')

# linux_distribution deprecated in py3.7
try:
from platform import linux_distribution
except ImportError:
from distro import linux_distribution

# Import salt libs
import salt.exceptions
import salt.log
import salt.utils
import salt.utils.network
import salt.utils.dns
import salt.ext.six as six
from salt.ext.six.moves import range

if salt.utils.is_windows():
import salt.utils.win_osinfo

# Solve the Chicken and egg problem where grains need to run before any
# of the modules are loaded and are generally available for any usage.
import salt.modules.cmdmod
import salt.modules.smbios

__salt__ = {
'cmd.run': salt.modules.cmdmod._run_quiet,
'cmd.retcode': salt.modules.cmdmod._retcode_quiet,
'cmd.run_all': salt.modules.cmdmod._run_all_quiet,
'smbios.records': salt.modules.smbios.records,
'smbios.get': salt.modules.smbios.get,
}
log = logging.getLogger(__name__)

HAS_WMI = False
if salt.utils.is_windows():
# attempt to import the python wmi module
# the Windows minion uses WMI for some of its grains
try:
import wmi # pylint: disable=import-error
import salt.utils.winapi
import win32api
import salt.modules.reg
HAS_WMI = True
__salt__['reg.read_value'] = salt.modules.reg.read_value
except ImportError:
log.exception(
'Unable to import Python wmi module, some core grains '
'will be missing'
)

_INTERFACES = {}


def _windows_cpudata():
'''
Return some CPU information on Windows minions
'''
# Provides:
# num_cpus
# cpu_model
grains = {}
if 'NUMBER_OF_PROCESSORS' in os.environ:
# Cast to int so that the logic isn't broken when used as a
# conditional in templating. Also follows _linux_cpudata()
try:
grains['num_cpus'] = int(os.environ['NUMBER_OF_PROCESSORS'])
except ValueError:
grains['num_cpus'] = 1
grains['cpu_model'] = __salt__['reg.read_value'](
"HKEY_LOCAL_MACHINE",
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
"ProcessorNameString").get('vdata')
return grains


def _linux_cpudata():
'''
Return some CPU information for Linux minions
'''
# Provides:
# num_cpus
# cpu_model
# cpu_flags
grains = {}
cpuinfo = '/proc/cpuinfo'
# Parse over the cpuinfo file
if os.path.isfile(cpuinfo):
with salt.utils.fopen(cpuinfo, 'r') as _fp:
for line in _fp:
comps = line.split(':')
if not len(comps) > 1:
continue
key = comps[0].strip()
val = comps[1].strip()
if key == 'processor':
grains['num_cpus'] = int(val) + 1
elif key == 'model name':
grains['cpu_model'] = val
elif key == 'flags':
grains['cpu_flags'] = val.split()
elif key == 'Features':
grains['cpu_flags'] = val.split()
# ARM support - /proc/cpuinfo
#
# Processor : ARMv6-compatible processor rev 7 (v6l)
# BogoMIPS : 697.95
# Features : swp half thumb fastmult vfp edsp java tls
# CPU implementer : 0x41
# CPU architecture: 7
# CPU variant : 0x0
# CPU part : 0xb76
# CPU revision : 7
#
# Hardware : BCM2708
# Revision : 0002
# Serial : 00000000
elif key == 'Processor':
grains['cpu_model'] = val.split('-')[0]
grains['num_cpus'] = 1
if 'num_cpus' not in grains:
grains['num_cpus'] = 0
if 'cpu_model' not in grains:
grains['cpu_model'] = 'Unknown'
if 'cpu_flags' not in grains:
grains['cpu_flags'] = []
return grains


def _linux_gpu_data():
'''
num_gpus: int
gpus:
- vendor: nvidia|amd|ati|...
model: string
'''
if __opts__.get('enable_lspci', True) is False:
return {}

if __opts__.get('enable_gpu_grains', True) is False:
return {}

lspci = salt.utils.which('lspci')
if not lspci:
log.debug(
'The `lspci` binary is not available on the system. GPU grains '
'will not be available.'
)
return {}

# dominant gpu vendors to search for (MUST be lowercase for matching below)
known_vendors = ['nvidia', 'amd', 'ati', 'intel']
gpu_classes = ('vga compatible controller', '3d controller')

devs = []
try:
lspci_out = __salt__['cmd.run']('{0} -vmm'.format(lspci))

cur_dev = {}
error = False
# Add a blank element to the lspci_out.splitlines() list,
# otherwise the last device is not evaluated as a cur_dev and ignored.
lspci_list = lspci_out.splitlines()
lspci_list.append('')
for line in lspci_list:
# check for record-separating empty lines
if line == '':
if cur_dev.get('Class', '').lower() in gpu_classes:
devs.append(cur_dev)
cur_dev = {}
continue
if re.match(r'^\w+:\s+.*', line):
key, val = line.split(':', 1)
cur_dev[key.strip()] = val.strip()
else:
error = True
log.debug('Unexpected lspci output: \'{0}\''.format(line))

if error:
log.warning(
'Error loading grains, unexpected linux_gpu_data output, '
'check that you have a valid shell configured and '
'permissions to run lspci command'
)
except OSError:
pass

gpus = []
for gpu in devs:
vendor_strings = gpu['Vendor'].lower().split()
# default vendor to 'unknown', overwrite if we match a known one
vendor = 'unknown'
for name in known_vendors:
# search for an 'expected' vendor name in the list of strings
if name in vendor_strings:
vendor = name
break
gpus.append({'vendor': vendor, 'model': gpu['Device']})

grains = {}
grains['num_gpus'] = len(gpus)
grains['gpus'] = gpus
return grains


def _netbsd_gpu_data():
'''
num_gpus: int
gpus:
- vendor: nvidia|amd|ati|...
model: string
'''
known_vendors = ['nvidia', 'amd', 'ati', 'intel', 'cirrus logic', 'vmware']

gpus = []
try:
pcictl_out = __salt__['cmd.run']('pcictl pci0 list')

for line in pcictl_out.splitlines():
for vendor in known_vendors:
vendor_match = re.match(
r'[0-9:]+ ({0}) (.+) \(VGA .+\)'.format(vendor),
line,
re.IGNORECASE
)
if vendor_match:
gpus.append({'vendor': vendor_match.group(1), 'model': vendor_match.group(2)})
except OSError:
pass

grains = {}
grains['num_gpus'] = len(gpus)
grains['gpus'] = gpus
return grains


def _osx_gpudata():
'''
num_gpus: int
gpus:
- vendor: nvidia|amd|ati|...
model: string
'''

gpus = []
try:
pcictl_out = __salt__['cmd.run']('system_profiler SPDisplaysDataType')

for line in pcictl_out.splitlines():
fieldname, _, fieldval = line.partition(': ')
if fieldname.strip() == "Chipset Model":
vendor, _, model = fieldval.partition(' ')
vendor = vendor.lower()
gpus.append({'vendor': vendor, 'model': model})

except OSError:
pass

grains = {}
grains['num_gpus'] = len(gpus)
grains['gpus'] = gpus
return grains


def _bsd_cpudata(osdata):
'''
Return CPU information for BSD-like systems
'''
# Provides:
# cpuarch
# num_cpus
# cpu_model
# cpu_flags
sysctl = salt.utils.which('sysctl')
arch = salt.utils.which('arch')
cmds = {}

if sysctl:
cmds.update({
'num_cpus': '{0} -n hw.ncpu'.format(sysctl),
'cpuarch': '{0} -n hw.machine'.format(sysctl),
'cpu_model': '{0} -n hw.model'.format(sysctl),
})

if arch and osdata['kernel'] == 'OpenBSD':
cmds['cpuarch'] = '{0} -s'.format(arch)

if osdata['kernel'] == 'Darwin':
cmds['cpu_model'] = '{0} -n machdep.cpu.brand_string'.format(sysctl)
cmds['cpu_flags'] = '{0} -n machdep.cpu.features'.format(sysctl)

grains = dict([(k, __salt__['cmd.run'](v)) for k, v in six.iteritems(cmds)])

if 'cpu_flags' in grains and isinstance(grains['cpu_flags'], six.string_types):
grains['cpu_flags'] = grains['cpu_flags'].split(' ')

if osdata['kernel'] == 'NetBSD':
grains['cpu_flags'] = []
for line in __salt__['cmd.run']('cpuctl identify 0').splitlines():
cpu_match = re.match(r'cpu[0-9]:\ features[0-9]?\ .+<(.+)>', line)
if cpu_match:
flag = cpu_match.group(1).split(',')
grains['cpu_flags'].extend(flag)

if osdata['kernel'] == 'FreeBSD' and os.path.isfile('/var/run/dmesg.boot'):
grains['cpu_flags'] = []
# TODO: at least it needs to be tested for BSD other then FreeBSD
with salt.utils.fopen('/var/run/dmesg.boot', 'r') as _fp:
cpu_here = False
for line in _fp:
if line.startswith('CPU: '):
cpu_here = True # starts CPU descr
continue
if cpu_here:
if not line.startswith(' '):
break # game over
if 'Features' in line:
start = line.find('<')
end = line.find('>')
if start > 0 and end > 0:
flag = line[start + 1:end].split(',')
grains['cpu_flags'].extend(flag)
try:
grains['num_cpus'] = int(grains['num_cpus'])
except ValueError:
grains['num_cpus'] = 1

return grains


def _sunos_cpudata():
'''
Return the CPU information for Solaris-like systems
'''
# Provides:
# cpuarch
# num_cpus
# cpu_model
# cpu_flags
grains = {}
grains['cpu_flags'] = []

grains['cpuarch'] = __salt__['cmd.run']('isainfo -k')
psrinfo = '/usr/sbin/psrinfo 2>/dev/null'
grains['num_cpus'] = len(__salt__['cmd.run'](psrinfo, python_shell=True).splitlines())
kstat_info = 'kstat -p cpu_info:*:*:brand'
for line in __salt__['cmd.run'](kstat_info).splitlines():
match = re.match(r'(\w+:\d+:\w+\d+:\w+)\s+(.+)', line)
if match:
grains['cpu_model'] = match.group(2)
isainfo = 'isainfo -n -v'
for line in __salt__['cmd.run'](isainfo).splitlines():
match = re.match(r'^\s+(.+)', line)
if match:
cpu_flags = match.group(1).split()
grains['cpu_flags'].extend(cpu_flags)

return grains


def _memdata(osdata):
'''
Gather information about the system memory
'''
# Provides:
# mem_total
grains = {'mem_total': 0}
if osdata['kernel'] == 'Linux':
meminfo = '/proc/meminfo'

if os.path.isfile(meminfo):
with salt.utils.fopen(meminfo, 'r') as ifile:
for line in ifile:
comps = line.rstrip('\n').split(':')
if not len(comps) > 1:
continue
if comps[0].strip() == 'MemTotal':
# Use floor division to force output to be an integer
grains['mem_total'] = int(comps[1].split()[0]) // 1024
elif osdata['kernel'] in ('FreeBSD', 'OpenBSD', 'NetBSD', 'Darwin'):
sysctl = salt.utils.which('sysctl')
if sysctl:
if osdata['kernel'] == 'Darwin':
mem = __salt__['cmd.run']('{0} -n hw.memsize'.format(sysctl))
else:
mem = __salt__['cmd.run']('{0} -n hw.physmem'.format(sysctl))
if osdata['kernel'] == 'NetBSD' and mem.startswith('-'):
mem = __salt__['cmd.run']('{0} -n hw.physmem64'.format(sysctl))
grains['mem_total'] = int(mem) / 1024 / 1024
elif osdata['kernel'] == 'SunOS':
prtconf = '/usr/sbin/prtconf 2>/dev/null'
for line in __salt__['cmd.run'](prtconf, python_shell=True).splitlines():
comps = line.split(' ')
if comps[0].strip() == 'Memory' and comps[1].strip() == 'size:':
grains['mem_total'] = int(comps[2].strip())
elif osdata['kernel'] == 'Windows' and HAS_WMI:
# get the Total Physical memory as reported by msinfo32
tot_bytes = win32api.GlobalMemoryStatusEx()['TotalPhys']
# return memory info in gigabytes
grains['mem_total'] = int(tot_bytes / (1024 ** 2))
return grains


def _windows_virtual(osdata):
'''
Returns what type of virtual hardware is under the hood, kvm or physical
'''
# Provides:
# virtual
# virtual_subtype
grains = dict()
if osdata['kernel'] != 'Windows':
return grains

# It is possible that the 'manufacturer' and/or 'productname' grains
# exist but have a value of None.
manufacturer = osdata.get('manufacturer', '')
if manufacturer is None:
manufacturer = ''
productname = osdata.get('productname', '')
if productname is None:
productname = ''

if 'QEMU' in manufacturer:
# FIXME: Make this detect between kvm or qemu
grains['virtual'] = 'kvm'
if 'Bochs' in manufacturer:
grains['virtual'] = 'kvm'
# Product Name: (oVirt) www.ovirt.org
# Red Hat Community virtualization Project based on kvm
elif 'oVirt' in productname:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'oVirt'
# Red Hat Enterprise Virtualization
elif 'RHEV Hypervisor' in productname:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'rhev'
# Product Name: VirtualBox
elif 'VirtualBox' in productname:
grains['virtual'] = 'VirtualBox'
# Product Name: VMware Virtual Platform
elif 'VMware Virtual Platform' in productname:
grains['virtual'] = 'VMware'
# Manufacturer: Microsoft Corporation
# Product Name: Virtual Machine
elif 'Microsoft' in manufacturer and \
'Virtual Machine' in productname:
grains['virtual'] = 'VirtualPC'
# Manufacturer: Parallels Software International Inc.
elif 'Parallels Software' in manufacturer:
grains['virtual'] = 'Parallels'
# Apache CloudStack
elif 'CloudStack KVM Hypervisor' in productname:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'cloudstack'
return grains


def _virtual(osdata):
'''
Returns what type of virtual hardware is under the hood, kvm or physical
'''
# This is going to be a monster, if you are running a vm you can test this
# grain with please submit patches!
# Provides:
# virtual
# virtual_subtype
grains = {'virtual': 'physical'}

# Skip the below loop on platforms which have none of the desired cmds
# This is a temporary measure until we can write proper virtual hardware
# detection.
skip_cmds = ('AIX',)

# list of commands to be executed to determine the 'virtual' grain
_cmds = ['systemd-detect-virt', 'virt-what', 'dmidecode']
# test first for virt-what, which covers most of the desired functionality
# on most platforms
if not salt.utils.is_windows() and osdata['kernel'] not in skip_cmds:
if salt.utils.which('virt-what'):
_cmds = ['virt-what']
else:
log.debug(
'Please install \'virt-what\' to improve results of the '
'\'virtual\' grain.'
)
# Check if enable_lspci is True or False
if __opts__.get('enable_lspci', True) is False:
# /proc/bus/pci does not exists, lspci will fail
if os.path.exists('/proc/bus/pci'):
_cmds += ['lspci']

# Add additional last resort commands
if osdata['kernel'] in skip_cmds:
_cmds = ()

# Quick backout for BrandZ (Solaris LX Branded zones)
# Don't waste time trying other commands to detect the virtual grain
if osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname():
grains['virtual'] = 'zone'
return grains

failed_commands = set()
for command in _cmds:
args = []
if osdata['kernel'] == 'Darwin':
command = 'system_profiler'
args = ['SPDisplaysDataType']
elif osdata['kernel'] == 'SunOS':
command = 'prtdiag'
args = []

cmd = salt.utils.which(command)

if not cmd:
continue

cmd = '{0} {1}'.format(cmd, ' '.join(args))

try:
ret = __salt__['cmd.run_all'](cmd)

if ret['retcode'] > 0:
if salt.log.is_logging_configured():
# systemd-detect-virt always returns > 0 on non-virtualized
# systems
# prtdiag only works in the global zone, skip if it fails
if salt.utils.is_windows() or 'systemd-detect-virt' in cmd or 'prtdiag' in cmd:
continue
failed_commands.add(command)
continue
except salt.exceptions.CommandExecutionError:
if salt.log.is_logging_configured():
if salt.utils.is_windows():
continue
failed_commands.add(command)
continue

output = ret['stdout']
if command == "system_profiler":
macoutput = output.lower()
if '0x1ab8' in macoutput:
grains['virtual'] = 'Parallels'
if 'parallels' in macoutput:
grains['virtual'] = 'Parallels'
if 'vmware' in macoutput:
grains['virtual'] = 'VMware'
if '0x15ad' in macoutput:
grains['virtual'] = 'VMware'
if 'virtualbox' in macoutput:
grains['virtual'] = 'VirtualBox'
# Break out of the loop so the next log message is not issued
break
elif command == 'systemd-detect-virt':
if output in ('qemu', 'kvm', 'oracle', 'xen', 'bochs', 'chroot', 'uml', 'systemd-nspawn'):
grains['virtual'] = output
break
elif 'vmware' in output:
grains['virtual'] = 'VMware'
break
elif 'microsoft' in output:
grains['virtual'] = 'VirtualPC'
break
elif 'lxc' in output:
grains['virtual'] = 'LXC'
break
elif 'systemd-nspawn' in output:
grains['virtual'] = 'LXC'
break
elif command == 'virt-what':
if output in ('kvm', 'qemu', 'uml', 'xen', 'lxc'):
grains['virtual'] = output
break
elif 'vmware' in output:
grains['virtual'] = 'VMware'
break
elif 'parallels' in output:
grains['virtual'] = 'Parallels'
break
elif 'hyperv' in output:
grains['virtual'] = 'HyperV'
break
elif command == 'dmidecode':
# Product Name: VirtualBox
if 'Vendor: QEMU' in output:
# FIXME: Make this detect between kvm or qemu
grains['virtual'] = 'kvm'
if 'Manufacturer: QEMU' in output:
grains['virtual'] = 'kvm'
if 'Vendor: Bochs' in output:
grains['virtual'] = 'kvm'
if 'Manufacturer: Bochs' in output:
grains['virtual'] = 'kvm'
if 'BHYVE' in output:
grains['virtual'] = 'bhyve'
# Product Name: (oVirt) www.ovirt.org
# Red Hat Community virtualization Project based on kvm
elif 'Manufacturer: oVirt' in output:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'ovirt'
# Red Hat Enterprise Virtualization
elif 'Product Name: RHEV Hypervisor' in output:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'rhev'
elif 'VirtualBox' in output:
grains['virtual'] = 'VirtualBox'
# Product Name: VMware Virtual Platform
elif 'VMware' in output:
grains['virtual'] = 'VMware'
# Manufacturer: Microsoft Corporation
# Product Name: Virtual Machine
elif ': Microsoft' in output and 'Virtual Machine' in output:
grains['virtual'] = 'VirtualPC'
# Manufacturer: Parallels Software International Inc.
elif 'Parallels Software' in output:
grains['virtual'] = 'Parallels'
elif 'Manufacturer: Google' in output:
grains['virtual'] = 'kvm'
# Proxmox KVM
elif 'Vendor: SeaBIOS' in output:
grains['virtual'] = 'kvm'
# Break out of the loop, lspci parsing is not necessary
break
elif command == 'lspci':
# dmidecode not available or the user does not have the necessary
# permissions
model = output.lower()
if 'vmware' in model:
grains['virtual'] = 'VMware'
# 00:04.0 System peripheral: InnoTek Systemberatung GmbH
# VirtualBox Guest Service
elif 'virtualbox' in model:
grains['virtual'] = 'VirtualBox'
elif 'qemu' in model:
grains['virtual'] = 'kvm'
elif 'virtio' in model:
grains['virtual'] = 'kvm'
# Break out of the loop so the next log message is not issued
break
elif command == 'virt-what':
# if 'virt-what' returns nothing, it's either an undetected platform
# so we default just as virt-what to 'physical', otherwise use the
# platform detected/returned by virt-what
if output:
grains['virtual'] = output.lower()
break
elif command == 'prtdiag':
model = output.lower().split("\n")[0]
if 'vmware' in model:
grains['virtual'] = 'VMware'
elif 'virtualbox' in model:
grains['virtual'] = 'VirtualBox'
elif 'qemu' in model:
grains['virtual'] = 'kvm'
elif 'joyent smartdc hvm' in model:
grains['virtual'] = 'kvm'
break
else:
if osdata['kernel'] not in skip_cmds:
log.debug(
'All tools for virtual hardware identification failed to '
'execute because they do not exist on the system running this '
'instance or the user does not have the necessary permissions '
'to execute them. Grains output might not be accurate.'
)

choices = ('Linux', 'HP-UX')
isdir = os.path.isdir
sysctl = salt.utils.which('sysctl')
if osdata['kernel'] in choices:
if os.path.isdir('/proc'):
try:
self_root = os.stat('/')
init_root = os.stat('/proc/1/root/.')
if self_root != init_root:
grains['virtual_subtype'] = 'chroot'
except (IOError, OSError):
pass
if os.path.isfile('/proc/1/cgroup'):
try:
with salt.utils.fopen('/proc/1/cgroup', 'r') as fhr:
if ':/lxc/' in fhr.read():
grains['virtual_subtype'] = 'LXC'
with salt.utils.fopen('/proc/1/cgroup', 'r') as fhr:
fhr_contents = fhr.read()
if ':/docker/' in fhr_contents or ':/system.slice/docker' in fhr_contents:
grains['virtual_subtype'] = 'Docker'
except IOError:
pass
if isdir('/proc/vz'):
if os.path.isfile('/proc/vz/version'):
grains['virtual'] = 'openvzhn'
elif os.path.isfile('/proc/vz/veinfo'):
grains['virtual'] = 'openvzve'
# a posteriori, it's expected for these to have failed:
failed_commands.discard('lspci')
failed_commands.discard('dmidecode')
# Provide additional detection for OpenVZ
if os.path.isfile('/proc/self/status'):
with salt.utils.fopen('/proc/self/status') as status_file:
vz_re = re.compile(r'^envID:\s+(\d+)$')
for line in status_file:
vz_match = vz_re.match(line.rstrip('\n'))
if vz_match and int(vz_match.groups()[0]) != 0:
grains['virtual'] = 'openvzve'
elif vz_match and int(vz_match.groups()[0]) == 0:
grains['virtual'] = 'openvzhn'
if isdir('/proc/sys/xen') or \
isdir('/sys/bus/xen') or isdir('/proc/xen'):
if os.path.isfile('/proc/xen/xsd_kva'):
# Tested on CentOS 5.3 / 2.6.18-194.26.1.el5xen
# Tested on CentOS 5.4 / 2.6.18-164.15.1.el5xen
grains['virtual_subtype'] = 'Xen Dom0'
else:
if grains.get('productname', '') == 'HVM domU':
# Requires dmidecode!
grains['virtual_subtype'] = 'Xen HVM DomU'
elif os.path.isfile('/proc/xen/capabilities') and \
os.access('/proc/xen/capabilities', os.R_OK):
with salt.utils.fopen('/proc/xen/capabilities') as fhr:
if 'control_d' not in fhr.read():
# Tested on CentOS 5.5 / 2.6.18-194.3.1.el5xen
grains['virtual_subtype'] = 'Xen PV DomU'
else:
# Shouldn't get to this, but just in case
grains['virtual_subtype'] = 'Xen Dom0'
# Tested on Fedora 10 / 2.6.27.30-170.2.82 with xen
# Tested on Fedora 15 / 2.6.41.4-1 without running xen
elif isdir('/sys/bus/xen'):
if 'xen:' in __salt__['cmd.run']('dmesg').lower():
grains['virtual_subtype'] = 'Xen PV DomU'
elif os.listdir('/sys/bus/xen/drivers'):
# An actual DomU will have several drivers
# whereas a paravirt ops kernel will not.
grains['virtual_subtype'] = 'Xen PV DomU'
# If a Dom0 or DomU was detected, obviously this is xen
if 'dom' in grains.get('virtual_subtype', '').lower():
grains['virtual'] = 'xen'
if os.path.isfile('/proc/cpuinfo'):
with salt.utils.fopen('/proc/cpuinfo', 'r') as fhr:
if 'QEMU Virtual CPU' in fhr.read():
grains['virtual'] = 'kvm'
if os.path.isfile('/sys/devices/virtual/dmi/id/product_name'):
try:
with salt.utils.fopen('/sys/devices/virtual/dmi/id/product_name', 'r') as fhr:
output = fhr.read()
if 'VirtualBox' in output:
grains['virtual'] = 'VirtualBox'
elif 'RHEV Hypervisor' in output:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'rhev'
elif 'oVirt Node' in output:
grains['virtual'] = 'kvm'
grains['virtual_subtype'] = 'ovirt'
elif 'Google' in output:
grains['virtual'] = 'gce'
except IOError:
pass
elif osdata['kernel'] == 'FreeBSD':
kenv = salt.utils.which('kenv')
if kenv:
product = __salt__['cmd.run'](
'{0} smbios.system.product'.format(kenv)
)
maker = __salt__['cmd.run'](
'{0} smbios.system.maker'.format(kenv)
)
if product.startswith('VMware'):
grains['virtual'] = 'VMware'
if product.startswith('VirtualBox'):
grains['virtual'] = 'VirtualBox'
if maker.startswith('Xen'):
grains['virtual_subtype'] = '{0} {1}'.format(maker, product)
grains['virtual'] = 'xen'
if maker.startswith('Microsoft') and product.startswith('Virtual'):
grains['virtual'] = 'VirtualPC'
if maker.startswith('OpenStack'):
grains['virtual'] = 'OpenStack'
if maker.startswith('Bochs'):
grains['virtual'] = 'kvm'
if sysctl:
hv_vendor = __salt__['cmd.run']('{0} hw.hv_vendor'.format(sysctl))
model = __salt__['cmd.run']('{0} hw.model'.format(sysctl))
jail = __salt__['cmd.run'](
'{0} -n security.jail.jailed'.format(sysctl)
)
if 'bhyve' in hv_vendor:
grains['virtual'] = 'bhyve'
if jail == '1':
grains['virtual_subtype'] = 'jail'
if 'QEMU Virtual CPU' in model:
grains['virtual'] = 'kvm'
elif osdata['kernel'] == 'OpenBSD':
if osdata['manufacturer'] == 'QEMU':
grains['virtual'] = 'kvm'
elif osdata['kernel'] == 'SunOS':
# Check if it's a "regular" zone. (i.e. Solaris 10/11 zone)
zonename = salt.utils.which('zonename')
if zonename:
zone = __salt__['cmd.run']('{0}'.format(zonename))
if zone != 'global':
grains['virtual'] = 'zone'
if salt.utils.is_smartos_zone():
grains.update(_smartos_zone_data())
# Check if it's a branded zone (i.e. Solaris 8/9 zone)
if isdir('/.SUNWnative'):
grains['virtual'] = 'zone'
elif osdata['kernel'] == 'NetBSD':
if sysctl:
if 'QEMU Virtual CPU' in __salt__['cmd.run'](
'{0} -n machdep.cpu_brand'.format(sysctl)):
grains['virtual'] = 'kvm'
elif 'invalid' not in __salt__['cmd.run'](
'{0} -n machdep.xen.suspend'.format(sysctl)):
grains['virtual'] = 'Xen PV DomU'
elif 'VMware' in __salt__['cmd.run'](
'{0} -n machdep.dmi.system-vendor'.format(sysctl)):
grains['virtual'] = 'VMware'
# NetBSD has Xen dom0 support
elif __salt__['cmd.run'](
'{0} -n machdep.idle-mechanism'.format(sysctl)) == 'xen':
if os.path.isfile('/var/run/xenconsoled.pid'):
grains['virtual_subtype'] = 'Xen Dom0'

for command in failed_commands:
log.info(
"Although '{0}' was found in path, the current user "
'cannot execute it. Grains output might not be '
'accurate.'.format(command)
)
return grains


def _ps(osdata):
'''
Return the ps grain
'''
grains = {}
bsd_choices = ('FreeBSD', 'NetBSD', 'OpenBSD', 'MacOS')
if osdata['os'] in bsd_choices:
grains['ps'] = 'ps auxwww'
elif osdata['os_family'] == 'Solaris':
grains['ps'] = '/usr/ucb/ps auxwww'
elif osdata['os'] == 'Windows':
grains['ps'] = 'tasklist.exe'
elif osdata.get('virtual', '') == 'openvzhn':
grains['ps'] = (
'ps -fH -p $(grep -l \"^envID:[[:space:]]*0\\$\" '
'/proc/[0-9]*/status | sed -e \"s=/proc/\\([0-9]*\\)/.*=\\1=\") '
'| awk \'{ $7=\"\"; print }\''
)
elif osdata['os_family'] == 'AIX':
grains['ps'] = '/usr/bin/ps auxww'
else:
grains['ps'] = 'ps -efHww'
return grains


def _clean_value(key, val):
'''
Clean out well-known bogus values.
If it isn't clean (for example has value 'None'), return None.
Otherwise, return the original value.

NOTE: This logic also exists in the smbios module. This function is
for use when not using smbios to retrieve the value.
'''
if (val is None or
not len(val) or
re.match('none', val, flags=re.IGNORECASE)):
return None
elif 'uuid' in key:
# Try each version (1-5) of RFC4122 to check if it's actually a UUID
for uuidver in range(1, 5):
try:
uuid.UUID(val, version=uuidver)
return val
except ValueError:
continue
log.trace('HW {0} value {1} is an invalid UUID'.format(key, val.replace('\n', ' ')))
return None
elif re.search('serial|part|version', key):
# 'To be filled by O.E.M.
# 'Not applicable' etc.
# 'Not specified' etc.
# 0000000, 1234567 etc.
# begone!
if (re.match(r'^[0]+$', val) or
re.match(r'[0]?1234567[8]?[9]?[0]?', val) or
re.search(r'sernum|part[_-]?number|specified|filled|applicable', val, flags=re.IGNORECASE)):
return None
elif re.search('asset|manufacturer', key):
# AssetTag0. Manufacturer04. Begone.
if re.search(r'manufacturer|to be filled|available|asset|^no(ne|t)', val, flags=re.IGNORECASE):
return None
else:
# map unspecified, undefined, unknown & whatever to None
if (re.search(r'to be filled', val, flags=re.IGNORECASE) or
re.search(r'un(known|specified)|no(t|ne)? (asset|provided|defined|available|present|specified)',
val, flags=re.IGNORECASE)):
return None
return val


def _windows_platform_data():
'''
Use the platform module for as much as we can.
'''
# Provides:
# kernelrelease
# kernelversion
# osversion
# osrelease
# osservicepack
# osmanufacturer
# manufacturer
# productname
# biosversion
# serialnumber
# osfullname
# timezone
# windowsdomain
# motherboard.productname
# motherboard.serialnumber
# virtual

if not HAS_WMI:
return {}

with salt.utils.winapi.Com():
wmi_c = wmi.WMI()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx
systeminfo = wmi_c.Win32_ComputerSystem()[0]
# https://msdn.microsoft.com/en-us/library/aa394239(v=vs.85).aspx
osinfo = wmi_c.Win32_OperatingSystem()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394077(v=vs.85).aspx
biosinfo = wmi_c.Win32_BIOS()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394498(v=vs.85).aspx
timeinfo = wmi_c.Win32_TimeZone()[0]

# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394072(v=vs.85).aspx
motherboard = {'product': None,
'serial': None}
try:
motherboardinfo = wmi_c.Win32_BaseBoard()[0]
motherboard['product'] = motherboardinfo.Product
motherboard['serial'] = motherboardinfo.SerialNumber
except IndexError:
log.debug('Motherboard info not available on this system')

os_release = platform.release()
kernel_version = platform.version()
info = salt.utils.win_osinfo.get_os_version_info()

# Starting with Python 2.7.12 and 3.5.2 the `platform.uname()` function
# started reporting the Desktop version instead of the Server version on
# Server versions of Windows, so we need to look those up
# Check for Python >=2.7.12 or >=3.5.2
ver = pythonversion()['pythonversion']
if ((six.PY2 and
salt.utils.compare_versions(ver, '>=', [2, 7, 12, 'final', 0]))
or
(six.PY3 and
salt.utils.compare_versions(ver, '>=', [3, 5, 2, 'final', 0]))):
# (Product Type 1 is Desktop, Everything else is Server)
if info['ProductType'] > 1:
server = {'Vista': '2008Server',
'7': '2008ServerR2',
'8': '2012Server',
'8.1': '2012ServerR2',
'10': '2016Server'}
os_release = server.get(os_release,
'Grain not found. Update lookup table '
'in the `_windows_platform_data` '
'function in `grains\\core.py`')

service_pack = None
if info['ServicePackMajor'] > 0:
service_pack = ''.join(['SP', str(info['ServicePackMajor'])])

grains = {
'kernelrelease': _clean_value('kernelrelease', osinfo.Version),
'kernelversion': _clean_value('kernelversion', kernel_version),
'osversion': _clean_value('osversion', osinfo.Version),
'osrelease': _clean_value('osrelease', os_release),
'osservicepack': _clean_value('osservicepack', service_pack),
'osmanufacturer': _clean_value('osmanufacturer', osinfo.Manufacturer),
'manufacturer': _clean_value('manufacturer', systeminfo.Manufacturer),
'productname': _clean_value('productname', systeminfo.Model),
# bios name had a bunch of whitespace appended to it in my testing
# 'PhoenixBIOS 4.0 Release 6.0 '
'biosversion': _clean_value('biosversion', biosinfo.Name.strip()),
'serialnumber': _clean_value('serialnumber', biosinfo.SerialNumber),
'osfullname': _clean_value('osfullname', osinfo.Caption),
'timezone': _clean_value('timezone', timeinfo.Description),
'windowsdomain': _clean_value('windowsdomain', systeminfo.Domain),
'motherboard': {
'productname': _clean_value('motherboard.productname', motherboard['product']),
'serialnumber': _clean_value('motherboard.serialnumber', motherboard['serial']),
}
}

# test for virtualized environments
# I only had VMware available so the rest are unvalidated
if 'VRTUAL' in biosinfo.Version: # (not a typo)
grains['virtual'] = 'HyperV'
elif 'A M I' in biosinfo.Version:
grains['virtual'] = 'VirtualPC'
elif 'VMware' in systeminfo.Model:
grains['virtual'] = 'VMware'
elif 'VirtualBox' in systeminfo.Model:
grains['virtual'] = 'VirtualBox'
elif 'Xen' in biosinfo.Version:
grains['virtual'] = 'Xen'
if 'HVM domU' in systeminfo.Model:
grains['virtual_subtype'] = 'HVM domU'
elif 'OpenStack' in systeminfo.Model:
grains['virtual'] = 'OpenStack'

return grains


def _osx_platform_data():
'''
Additional data for macOS systems
Returns: A dictionary containing values for the following:
- model_name
- boot_rom_version
- smc_version
- system_serialnumber
'''
cmd = 'system_profiler SPHardwareDataType'
hardware = __salt__['cmd.run'](cmd)

grains = {}
for line in hardware.splitlines():
field_name, _, field_val = line.partition(': ')
if field_name.strip() == "Model Name":
key = 'model_name'
grains[key] = _clean_value(key, field_val)
if field_name.strip() == "Boot ROM Version":
key = 'boot_rom_version'
grains[key] = _clean_value(key, field_val)
if field_name.strip() == "SMC Version (system)":
key = 'smc_version'
grains[key] = _clean_value(key, field_val)
if field_name.strip() == "Serial Number (system)":
key = 'system_serialnumber'
grains[key] = _clean_value(key, field_val)

return grains


def id_():
'''
Return the id
'''
return {'id': __opts__.get('id', '')}

_REPLACE_LINUX_RE = re.compile(r'\W(?:gnu/)?linux', re.IGNORECASE)

# This maps (at most) the first ten characters (no spaces, lowercased) of
# 'osfullname' to the 'os' grain that Salt traditionally uses.
# Please see os_data() and _supported_dists.
# If your system is not detecting properly it likely needs an entry here.
_OS_NAME_MAP = {
'redhatente': 'RedHat',
'gentoobase': 'Gentoo',
'archarm': 'Arch ARM',
'arch': 'Arch',
'debian': 'Debian',
'raspbian': 'Raspbian',
'fedoraremi': 'Fedora',
'chapeau': 'Chapeau',
'korora': 'Korora',
'amazonami': 'Amazon',
'alt': 'ALT',
'enterprise': 'OEL',
'oracleserv': 'OEL',
'cloudserve': 'CloudLinux',
'cloudlinux': 'CloudLinux',
'pidora': 'Fedora',
'scientific': 'ScientificLinux',
'synology': 'Synology',
'nilrt': 'NILinuxRT',
'nilrt-xfce': 'NILinuxRT-XFCE',
'manjaro': 'Manjaro',
'antergos': 'Antergos',
'sles': 'SUSE',
'slesexpand': 'RES',
'void': 'Void',
'linuxmint': 'Mint',
'neon': 'KDE neon',
}

# Map the 'os' grain to the 'os_family' grain
# These should always be capitalized entries as the lookup comes
# post-_OS_NAME_MAP. If your system is having trouble with detection, please
# make sure that the 'os' grain is capitalized and working correctly first.
_OS_FAMILY_MAP = {
'Ubuntu': 'Debian',
'Fedora': 'RedHat',
'Chapeau': 'RedHat',
'Korora': 'RedHat',
'FedBerry': 'RedHat',
'CentOS': 'RedHat',
'GoOSe': 'RedHat',
'Scientific': 'RedHat',
'Amazon': 'RedHat',
'CloudLinux': 'RedHat',
'OVS': 'RedHat',
'OEL': 'RedHat',
'XCP': 'RedHat',
'XenServer': 'RedHat',
'RES': 'RedHat',
'Sangoma': 'RedHat',
'Mandrake': 'Mandriva',
'ESXi': 'VMware',
'Mint': 'Debian',
'VMwareESX': 'VMware',
'Bluewhite64': 'Bluewhite',
'Slamd64': 'Slackware',
'SLES': 'Suse',
'SUSE Enterprise Server': 'Suse',
'SUSE Enterprise Server': 'Suse',
'SLED': 'Suse',
'openSUSE': 'Suse',
'SUSE': 'Suse',
'openSUSE Leap': 'Suse',
'openSUSE Tumbleweed': 'Suse',
'SLES_SAP': 'Suse',
'Solaris': 'Solaris',
'SmartOS': 'Solaris',
'OmniOS': 'Solaris',
'OpenIndiana Development': 'Solaris',
'OpenIndiana': 'Solaris',
'OpenSolaris Development': 'Solaris',
'OpenSolaris': 'Solaris',
'Oracle Solaris': 'Solaris',
'Arch ARM': 'Arch',
'Manjaro': 'Arch',
'Antergos': 'Arch',
'ALT': 'RedHat',
'Trisquel': 'Debian',
'GCEL': 'Debian',
'Linaro': 'Debian',
'elementary OS': 'Debian',
'ScientificLinux': 'RedHat',
'Raspbian': 'Debian',
'Devuan': 'Debian',
'antiX': 'Debian',
'NILinuxRT': 'NILinuxRT',
'NILinuxRT-XFCE': 'NILinuxRT',
'KDE neon': 'Debian',
'Void': 'Void',
}


def _linux_bin_exists(binary):
'''
Does a binary exist in linux (depends on which, type, or whereis)
'''
for search_cmd in ('which', 'type -ap'):
try:
return __salt__['cmd.retcode'](
'{0} {1}'.format(search_cmd, binary)
) == 0
except salt.exceptions.CommandExecutionError:
pass

try:
return len(__salt__['cmd.run_all'](
'whereis -b {0}'.format(binary)
)['stdout'].split()) > 1
except salt.exceptions.CommandExecutionError:
return False


def _get_interfaces():
'''
Provide a dict of the connected interfaces and their ip addresses
'''

global _INTERFACES
if not _INTERFACES:
_INTERFACES = salt.utils.network.interfaces()
return _INTERFACES


def _parse_os_release():
'''
Parse /etc/os-release and return a parameter dictionary

See http://www.freedesktop.org/software/systemd/man/os-release.html
for specification of the file format.
'''

filename = '/etc/os-release'
if not os.path.isfile(filename):
filename = '/usr/lib/os-release'

data = dict()
with salt.utils.fopen(filename) as ifile:
regex = re.compile('^([\\w]+)=(?:\'|")?(.*?)(?:\'|")?$')
for line in ifile:
match = regex.match(line.strip())
if match:
# Shell special characters ("$", quotes, backslash, backtick)
# are escaped with backslashes
data[match.group(1)] = re.sub(r'\\([$"\'\\`])', r'\1', match.group(2))

return data


def os_data():
'''
Return grains pertaining to the operating system
'''
grains = {
'num_gpus': 0,
'gpus': [],
}

# Windows Server 2008 64-bit
# ('Windows', 'MINIONNAME', '2008ServerR2', '6.1.7601', 'AMD64',
# 'Intel64 Fam ily 6 Model 23 Stepping 6, GenuineIntel')
# Ubuntu 10.04
# ('Linux', 'MINIONNAME', '2.6.32-38-server',
# '#83-Ubuntu SMP Wed Jan 4 11:26:59 UTC 2012', 'x86_64', '')

# pylint: disable=unpacking-non-sequence
(grains['kernel'], grains['nodename'],
grains['kernelrelease'], grains['kernelversion'], grains['cpuarch'], _) = platform.uname()
# pylint: enable=unpacking-non-sequence

if salt.utils.is_proxy():
grains['kernel'] = 'proxy'
grains['kernelrelease'] = 'proxy'
grains['kernelversion'] = 'proxy'
grains['osrelease'] = 'proxy'
grains['os'] = 'proxy'
grains['os_family'] = 'proxy'
grains['osfullname'] = 'proxy'
elif salt.utils.is_windows():
grains['os'] = 'Windows'
grains['os_family'] = 'Windows'
grains.update(_memdata(grains))
grains.update(_windows_platform_data())
grains.update(_windows_cpudata())
grains.update(_windows_virtual(grains))
grains.update(_ps(grains))

if 'Server' in grains['osrelease']:
osrelease_info = grains['osrelease'].split('Server', 1)
osrelease_info[1] = osrelease_info[1].lstrip('R')
else:
osrelease_info = grains['osrelease'].split('.')

for idx, value in enumerate(osrelease_info):
if not value.isdigit():
continue
osrelease_info[idx] = int(value)
grains['osrelease_info'] = tuple(osrelease_info)

grains['osfinger'] = '{os}-{ver}'.format(
os=grains['os'],
ver=grains['osrelease'])

grains['init'] = 'Windows'

return grains
elif salt.utils.is_linux():
# Add SELinux grain, if you have it
if _linux_bin_exists('selinuxenabled'):
grains['selinux'] = {}
grains['selinux']['enabled'] = __salt__['cmd.retcode'](
'selinuxenabled'
) == 0
if _linux_bin_exists('getenforce'):
grains['selinux']['enforced'] = __salt__['cmd.run'](
'getenforce'
).strip()

# Add systemd grain, if you have it
if _linux_bin_exists('systemctl') and _linux_bin_exists('localectl'):
grains['systemd'] = {}
systemd_info = __salt__['cmd.run'](
'systemctl --version'
).splitlines()
grains['systemd']['version'] = systemd_info[0].split()[1]
grains['systemd']['features'] = systemd_info[1]

# Add init grain
grains['init'] = 'unknown'
try:
os.stat('/run/systemd/system')
grains['init'] = 'systemd'
except (OSError, IOError):
if os.path.exists('/proc/1/cmdline'):
with salt.utils.fopen('/proc/1/cmdline') as fhr:
init_cmdline = fhr.read().replace('\x00', ' ').split()
try:
init_bin = salt.utils.which(init_cmdline[0])
except IndexError:
# Emtpy init_cmdline
init_bin = None
log.warning(
"Unable to fetch data from /proc/1/cmdline"
)
if init_bin is not None and init_bin.endswith('bin/init'):
supported_inits = (six.b('upstart'), six.b('sysvinit'), six.b('systemd'))
edge_len = max(len(x) for x in supported_inits) - 1
try:
buf_size = __opts__['file_buffer_size']
except KeyError:
# Default to the value of file_buffer_size for the minion
buf_size = 262144
try:
with salt.utils.fopen(init_bin, 'rb') as fp_:
buf = True
edge = six.b('')
buf = fp_.read(buf_size).lower()
while buf:
buf = edge + buf
for item in supported_inits:
if item in buf:
if six.PY3:
item = item.decode('utf-8')
grains['init'] = item
buf = six.b('')
break
edge = buf[-edge_len:]
buf = fp_.read(buf_size).lower()
except (IOError, OSError) as exc:
log.error(
'Unable to read from init_bin ({0}): {1}'
.format(init_bin, exc)
)
elif salt.utils.which('supervisord') in init_cmdline:
grains['init'] = 'supervisord'
elif init_cmdline == ['runit']:
grains['init'] = 'runit'
else:
log.info(
'Could not determine init system from command line: ({0})'
.format(' '.join(init_cmdline))
)

# Add lsb grains on any distro with lsb-release
try:
import lsb_release # pylint: disable=import-error
release = lsb_release.get_distro_information()
for key, value in six.iteritems(release):
key = key.lower()
lsb_param = 'lsb_{0}{1}'.format(
'' if key.startswith('distrib_') else 'distrib_',
key
)
grains[lsb_param] = value
# Catch a NameError to workaround possible breakage in lsb_release
# See https://github.com/saltstack/salt/issues/37867
except (ImportError, NameError):
# if the python library isn't available, default to regex
if os.path.isfile('/etc/lsb-release'):
# Matches any possible format:
# DISTRIB_ID="Ubuntu"
# DISTRIB_ID='Mageia'
# DISTRIB_ID=Fedora
# DISTRIB_RELEASE='10.10'
# DISTRIB_CODENAME='squeeze'
# DISTRIB_DESCRIPTION='Ubuntu 10.10'
regex = re.compile((
'^(DISTRIB_(?:ID|RELEASE|CODENAME|DESCRIPTION))=(?:\'|")?'
'([\\w\\s\\.\\-_]+)(?:\'|")?'
))
with salt.utils.fopen('/etc/lsb-release') as ifile:
for line in ifile:
match = regex.match(line.rstrip('\n'))
if match:
# Adds:
# lsb_distrib_{id,release,codename,description}
grains[
'lsb_{0}'.format(match.groups()[0].lower())
] = match.groups()[1].rstrip()
if grains.get('lsb_distrib_description', '').lower().startswith('antergos'):
# Antergos incorrectly configures their /etc/lsb-release,
# setting the DISTRIB_ID to "Arch". This causes the "os" grain
# to be incorrectly set to "Arch".
grains['osfullname'] = 'Antergos Linux'
elif 'lsb_distrib_id' not in grains:
if os.path.isfile('/etc/os-release') or os.path.isfile('/usr/lib/os-release'):
os_release = _parse_os_release()
if 'NAME' in os_release:
grains['lsb_distrib_id'] = os_release['NAME'].strip()
if 'VERSION_ID' in os_release:
grains['lsb_distrib_release'] = os_release['VERSION_ID']
if 'PRETTY_NAME' in os_release:
grains['lsb_distrib_codename'] = os_release['PRETTY_NAME']
if 'CPE_NAME' in os_release:
if ":suse:" in os_release['CPE_NAME'] or ":opensuse:" in os_release['CPE_NAME']:
grains['os'] = "SUSE"
# openSUSE `osfullname` grain normalization
if os_release.get("NAME") == "openSUSE Leap":
grains['osfullname'] = "Leap"
elif os_release.get("VERSION") == "Tumbleweed":
grains['osfullname'] = os_release["VERSION"]
elif os.path.isfile('/etc/SuSE-release'):
grains['lsb_distrib_id'] = 'SUSE'
version = ''
patch = ''
with salt.utils.fopen('/etc/SuSE-release') as fhr:
for line in fhr:
if 'enterprise' in line.lower():
grains['lsb_distrib_id'] = 'SLES'
grains['lsb_distrib_codename'] = re.sub(r'\(.+\)', '', line).strip()
elif 'version' in line.lower():
version = re.sub(r'[^0-9]', '', line)
elif 'patchlevel' in line.lower():
patch = re.sub(r'[^0-9]', '', line)
grains['lsb_distrib_release'] = version
if patch:
grains['lsb_distrib_release'] += '.' + patch
patchstr = 'SP' + patch
if grains['lsb_distrib_codename'] and patchstr not in grains['lsb_distrib_codename']:
grains['lsb_distrib_codename'] += ' ' + patchstr
if not grains.get('lsb_distrib_codename'):
grains['lsb_distrib_codename'] = 'n.a'
elif os.path.isfile('/etc/altlinux-release'):
# ALT Linux
grains['lsb_distrib_id'] = 'altlinux'
with salt.utils.fopen('/etc/altlinux-release') as ifile:
# This file is symlinked to from:
# /etc/fedora-release
# /etc/redhat-release
# /etc/system-release
for line in ifile:
# ALT Linux Sisyphus (unstable)
comps = line.split()
if comps[0] == 'ALT':
grains['lsb_distrib_release'] = comps[2]
grains['lsb_distrib_codename'] = \
comps[3].replace('(', '').replace(')', '')
elif os.path.isfile('/etc/centos-release'):
# CentOS Linux
grains['lsb_distrib_id'] = 'CentOS'
with salt.utils.fopen('/etc/centos-release') as ifile:
for line in ifile:
# Need to pull out the version and codename
# in the case of custom content in /etc/centos-release
find_release = re.compile(r'\d+\.\d+')
find_codename = re.compile(r'(?<=\()(.*?)(?=\))')
release = find_release.search(line)
codename = find_codename.search(line)
if release is not None:
grains['lsb_distrib_release'] = release.group()
if codename is not None:
grains['lsb_distrib_codename'] = codename.group()
elif os.path.isfile('/etc.defaults/VERSION') \
and os.path.isfile('/etc.defaults/synoinfo.conf'):
grains['osfullname'] = 'Synology'
with salt.utils.fopen('/etc.defaults/VERSION', 'r') as fp_:
synoinfo = {}
for line in fp_:
try:
key, val = line.rstrip('\n').split('=')
except ValueError:
continue
if key in ('majorversion', 'minorversion',
'buildnumber'):
synoinfo[key] = val.strip('"')
if len(synoinfo) != 3:
log.warning(
'Unable to determine Synology version info. '
'Please report this, as it is likely a bug.'
)
else:
grains['osrelease'] = (
'{majorversion}.{minorversion}-{buildnumber}'
.format(**synoinfo)
)

# Use the already intelligent platform module to get distro info
# (though apparently it's not intelligent enough to strip quotes)
(osname, osrelease, oscodename) = \
[x.strip('"').strip("'") for x in
linux_distribution(supported_dists=_supported_dists)]
# Try to assign these three names based on the lsb info, they tend to
# be more accurate than what python gets from /etc/DISTRO-release.
# It's worth noting that Ubuntu has patched their Python distribution
# so that linux_distribution() does the /etc/lsb-release parsing, but
# we do it anyway here for the sake for full portability.
if 'osfullname' not in grains:
grains['osfullname'] = \
grains.get('lsb_distrib_id', osname).strip()
if 'osrelease' not in grains:
# NOTE: This is a workaround for CentOS 7 os-release bug
# https://bugs.centos.org/view.php?id=8359
# /etc/os-release contains no minor distro release number so we fall back to parse
# /etc/centos-release file instead.
# Commit introducing this comment should be reverted after the upstream bug is released.
if 'CentOS Linux 7' in grains.get('lsb_distrib_codename', ''):
grains.pop('lsb_distrib_release', None)
grains['osrelease'] = \
grains.get('lsb_distrib_release', osrelease).strip()
grains['oscodename'] = grains.get('lsb_distrib_codename', '').strip() or oscodename
if 'Red Hat' in grains['oscodename']:
grains['oscodename'] = oscodename
distroname = _REPLACE_LINUX_RE.sub('', grains['osfullname']).strip()
# return the first ten characters with no spaces, lowercased
shortname = distroname.replace(' ', '').lower()[:10]
# this maps the long names from the /etc/DISTRO-release files to the
# traditional short names that Salt has used.
if 'os' not in grains:
grains['os'] = _OS_NAME_MAP.get(shortname, distroname)
grains.update(_linux_cpudata())
grains.update(_linux_gpu_data())
elif grains['kernel'] == 'SunOS':
if salt.utils.is_smartos():
# See https://github.com/joyent/smartos-live/issues/224
uname_v = os.uname()[3] # format: joyent_20161101T004406Z
uname_v = uname_v[uname_v.index('_')+1:]
grains['os'] = grains['osfullname'] = 'SmartOS'
# store a parsed version of YYYY.MM.DD as osrelease
grains['osrelease'] = ".".join([
uname_v.split('T')[0][0:4],
uname_v.split('T')[0][4:6],
uname_v.split('T')[0][6:8],
])
# store a untouched copy of the timestamp in osrelease_stamp
grains['osrelease_stamp'] = uname_v
if salt.utils.is_smartos_globalzone():
grains.update(_smartos_computenode_data())
elif os.path.isfile('/etc/release'):
with salt.utils.fopen('/etc/release', 'r') as fp_:
rel_data = fp_.read()
try:
release_re = re.compile(
r'((?:Open|Oracle )?Solaris|OpenIndiana|OmniOS) (Development)?'
r'\s*(\d+\.?\d*|v\d+)\s?[A-Z]*\s?(r\d+|\d+\/\d+|oi_\S+|snv_\S+)?'
)
osname, development, osmajorrelease, osminorrelease = \
release_re.search(rel_data).groups()
except AttributeError:
# Set a blank osrelease grain and fallback to 'Solaris'
# as the 'os' grain.
grains['os'] = grains['osfullname'] = 'Solaris'
grains['osrelease'] = ''
else:
if development is not None:
osname = ' '.join((osname, development))
uname_v = os.uname()[3]
grains['os'] = grains['osfullname'] = osname
if osname in ['Oracle Solaris'] and uname_v.startswith(osmajorrelease):
# Oracla Solars 11 and up have minor version in uname
grains['osrelease'] = uname_v
elif osname in ['OmniOS']:
# OmniOS
osrelease = []
osrelease.append(osmajorrelease[1:])
osrelease.append(osminorrelease[1:])
grains['osrelease'] = ".".join(osrelease)
grains['osrelease_stamp'] = uname_v
else:
# Sun Solaris 10 and earlier/comparable
osrelease = []
osrelease.append(osmajorrelease)
if osminorrelease:
osrelease.append(osminorrelease)
grains['osrelease'] = ".".join(osrelease)
grains['osrelease_stamp'] = uname_v

grains.update(_sunos_cpudata())
elif grains['kernel'] == 'VMkernel':
grains['os'] = 'ESXi'
elif grains['kernel'] == 'Darwin':
osrelease = __salt__['cmd.run']('sw_vers -productVersion')
osname = __salt__['cmd.run']('sw_vers -productName')
osbuild = __salt__['cmd.run']('sw_vers -buildVersion')
grains['os'] = 'MacOS'
grains['os_family'] = 'MacOS'
grains['osfullname'] = "{0} {1}".format(osname, osrelease)
grains['osrelease'] = osrelease
grains['osbuild'] = osbuild
grains['init'] = 'launchd'
grains.update(_bsd_cpudata(grains))
grains.update(_osx_gpudata())
grains.update(_osx_platform_data())
else:
grains['os'] = grains['kernel']
if grains['kernel'] == 'FreeBSD':
try:
grains['osrelease'] = __salt__['cmd.run']('freebsd-version -u').split('-')[0]
except salt.exceptions.CommandExecutionError:
# freebsd-version was introduced in 10.0.
# derive osrelease from kernelversion prior to that
grains['osrelease'] = grains['kernelrelease'].split('-')[0]
grains.update(_bsd_cpudata(grains))
if grains['kernel'] in ('OpenBSD', 'NetBSD'):
grains.update(_bsd_cpudata(grains))
grains['osrelease'] = grains['kernelrelease'].split('-')[0]
if grains['kernel'] == 'NetBSD':
grains.update(_netbsd_gpu_data())
if not grains['os']:
grains['os'] = 'Unknown {0}'.format(grains['kernel'])
grains['os_family'] = 'Unknown'
else:
# this assigns family names based on the os name
# family defaults to the os name if not found
grains['os_family'] = _OS_FAMILY_MAP.get(grains['os'],
grains['os'])

# Build the osarch grain. This grain will be used for platform-specific
# considerations such as package management. Fall back to the CPU
# architecture.
if grains.get('os_family') == 'Debian':
osarch = __salt__['cmd.run']('dpkg --print-architecture').strip()
elif grains.get('os_family') == 'RedHat':
osarch = __salt__['cmd.run']('rpm --eval %{_host_cpu}').strip()
elif grains.get('os_family') == 'NILinuxRT':
archinfo = {}
for line in __salt__['cmd.run']('opkg print-architecture').splitlines():
if line.startswith('arch'):
_, arch, priority = line.split()
archinfo[arch.strip()] = int(priority.strip())

# Return osarch in priority order (higher to lower)
osarch = sorted(archinfo, key=archinfo.get, reverse=True)
else:
osarch = grains['cpuarch']
grains['osarch'] = osarch

grains.update(_memdata(grains))

# Get the hardware and bios data
grains.update(_hw_data(grains))

# Get zpool data
grains.update(_zpool_data(grains))

# Load the virtual machine info
grains.update(_virtual(grains))
grains.update(_ps(grains))

if grains.get('osrelease', ''):
osrelease_info = grains['osrelease'].split('.')
for idx, value in enumerate(osrelease_info):
if not value.isdigit():
continue
osrelease_info[idx] = int(value)
grains['osrelease_info'] = tuple(osrelease_info)
try:
grains['osmajorrelease'] = int(grains['osrelease_info'][0])
except (IndexError, TypeError, ValueError):
log.debug(
'Unable to derive osmajorrelease from osrelease_info \'%s\'. '
'The osmajorrelease grain will not be set.',
grains['osrelease_info']
)
os_name = grains['os' if grains.get('os') in (
'FreeBSD', 'OpenBSD', 'NetBSD', 'Mac', 'Raspbian') else 'osfullname']
grains['osfinger'] = '{0}-{1}'.format(
os_name, grains['osrelease'] if os_name in ('Ubuntu',) else grains['osrelease_info'][0])

return grains


def locale_info():
'''
Provides
defaultlanguage
defaultencoding
'''
grains = {}
grains['locale_info'] = {}

if salt.utils.is_proxy():
return grains

try:
(
grains['locale_info']['defaultlanguage'],
grains['locale_info']['defaultencoding']
) = locale.getdefaultlocale()
except Exception:
# locale.getdefaultlocale can ValueError!! Catch anything else it
# might do, per #2205
grains['locale_info']['defaultlanguage'] = 'unknown'
grains['locale_info']['defaultencoding'] = 'unknown'
grains['locale_info']['detectedencoding'] = __salt_system_encoding__
return grains


def hostname():
'''
Return fqdn, hostname, domainname
'''
# This is going to need some work
# Provides:
# fqdn
# host
# localhost
# domain
global __FQDN__
grains = {}

if salt.utils.is_proxy():
return grains

grains['localhost'] = socket.gethostname()
if __FQDN__ is None:
__FQDN__ = salt.utils.network.get_fqhostname()

# On some distros (notably FreeBSD) if there is no hostname set
# salt.utils.network.get_fqhostname() will return None.
# In this case we punt and log a message at error level, but force the
# hostname and domain to be localhost.localdomain
# Otherwise we would stacktrace below
if __FQDN__ is None: # still!
log.error('Having trouble getting a hostname. Does this machine have its hostname and domain set properly?')
__FQDN__ = 'localhost.localdomain'

grains['fqdn'] = __FQDN__
(grains['host'], grains['domain']) = grains['fqdn'].partition('.')[::2]
return grains


def append_domain():
'''
Return append_domain if set
'''

grain = {}

if salt.utils.is_proxy():
return grain

if 'append_domain' in __opts__:
grain['append_domain'] = __opts__['append_domain']
return grain


def ip_fqdn():
'''
Return ip address and FQDN grains
'''
if salt.utils.is_proxy():
return {}

ret = {}
ret['ipv4'] = salt.utils.network.ip_addrs(include_loopback=True)
ret['ipv6'] = salt.utils.network.ip_addrs6(include_loopback=True)

_fqdn = hostname()['fqdn']
for socket_type, ipv_num in ((socket.AF_INET, '4'), (socket.AF_INET6, '6')):
key = 'fqdn_ip' + ipv_num
if not ret['ipv' + ipv_num]:
ret[key] = []
else:
try:
info = socket.getaddrinfo(_fqdn, None, socket_type)
ret[key] = list(set(item[4][0] for item in info))
except socket.error:
if __opts__['__role'] == 'master':
log.warning('Unable to find IPv{0} record for "{1}" causing a 10 second timeout when rendering grains. '
'Set the dns or /etc/hosts for IPv{0} to clear this.'.format(ipv_num, _fqdn))
ret[key] = []

return ret


def ip_interfaces():
'''
Provide a dict of the connected interfaces and their ip addresses
The addresses will be passed as a list for each interface
'''
# Provides:
# ip_interfaces

if salt.utils.is_proxy():
return {}

ret = {}
ifaces = _get_interfaces()
for face in ifaces:
iface_ips = []
for inet in ifaces[face].get('inet', []):
if 'address' in inet:
iface_ips.append(inet['address'])
for inet in ifaces[face].get('inet6', []):
if 'address' in inet:
iface_ips.append(inet['address'])
for secondary in ifaces[face].get('secondary', []):
if 'address' in secondary:
iface_ips.append(secondary['address'])
ret[face] = iface_ips
return {'ip_interfaces': ret}


def ip4_interfaces():
'''
Provide a dict of the connected interfaces and their ip4 addresses
The addresses will be passed as a list for each interface
'''
# Provides:
# ip_interfaces

if salt.utils.is_proxy():
return {}

ret = {}
ifaces = _get_interfaces()
for face in ifaces:
iface_ips = []
for inet in ifaces[face].get('inet', []):
if 'address' in inet:
iface_ips.append(inet['address'])
for secondary in ifaces[face].get('secondary', []):
if 'address' in secondary:
iface_ips.append(secondary['address'])
ret[face] = iface_ips
return {'ip4_interfaces': ret}


def ip6_interfaces():
'''
Provide a dict of the connected interfaces and their ip6 addresses
The addresses will be passed as a list for each interface
'''
# Provides:
# ip_interfaces

if salt.utils.is_proxy():
return {}

ret = {}
ifaces = _get_interfaces()
for face in ifaces:
iface_ips = []
for inet in ifaces[face].get('inet6', []):
if 'address' in inet:
iface_ips.append(inet['address'])
for secondary in ifaces[face].get('secondary', []):
if 'address' in secondary:
iface_ips.append(secondary['address'])
ret[face] = iface_ips
return {'ip6_interfaces': ret}


def hwaddr_interfaces():
'''
Provide a dict of the connected interfaces and their
hw addresses (Mac Address)
'''
# Provides:
# hwaddr_interfaces
ret = {}
ifaces = _get_interfaces()
for face in ifaces:
if 'hwaddr' in ifaces[face]:
ret[face] = ifaces[face]['hwaddr']
return {'hwaddr_interfaces': ret}


def dns():
'''
Parse the resolver configuration file

.. versionadded:: 2016.3.0
'''
# Provides:
# dns
if salt.utils.is_windows() or 'proxyminion' in __opts__:
return {}

resolv = salt.utils.dns.parse_resolv()
for key in ('nameservers', 'ip4_nameservers', 'ip6_nameservers',
'sortlist'):
if key in resolv:
resolv[key] = [str(i) for i in resolv[key]]

return {'dns': resolv} if resolv else {}


def get_machine_id():
'''
Provide the machine-id
'''
# Provides:
# machine-id
locations = ['/etc/machine-id', '/var/lib/dbus/machine-id']
existing_locations = [loc for loc in locations if os.path.exists(loc)]
if not existing_locations:
return {}
else:
with salt.utils.fopen(existing_locations[0]) as machineid:
return {'machine_id': machineid.read().strip()}


def path():
'''
Return the path
'''
# Provides:
# path
return {'path': os.environ.get('PATH', '').strip()}


def pythonversion():
'''
Return the Python version
'''
# Provides:
# pythonversion
return {'pythonversion': list(sys.version_info)}


def pythonpath():
'''
Return the Python path
'''
# Provides:
# pythonpath
return {'pythonpath': sys.path}


def pythonexecutable():
'''
Return the python executable in use
'''
# Provides:
# pythonexecutable
return {'pythonexecutable': sys.executable}


def saltpath():
'''
Return the path of the salt module
'''
# Provides:
# saltpath
salt_path = os.path.abspath(os.path.join(__file__, os.path.pardir))
return {'saltpath': os.path.dirname(salt_path)}


def saltversion():
'''
Return the version of salt
'''
# Provides:
# saltversion
from salt.version import __version__
return {'saltversion': __version__}


def zmqversion():
'''
Return the zeromq version
'''
# Provides:
# zmqversion
try:
import zmq
return {'zmqversion': zmq.zmq_version()} # pylint: disable=no-member
except ImportError:
return {}


def saltversioninfo():
'''
Return the version_info of salt

.. versionadded:: 0.17.0
'''
# Provides:
# saltversioninfo
from salt.version import __version_info__
return {'saltversioninfo': list(__version_info__)}


def _hw_data(osdata):
'''
Get system specific hardware data from dmidecode

Provides
biosversion
productname
manufacturer
serialnumber
biosreleasedate
uuid

.. versionadded:: 0.9.5
'''

if salt.utils.is_proxy():
return {}

grains = {}
if osdata['kernel'] == 'Linux' and os.path.exists('/sys/class/dmi/id'):
# On many Linux distributions basic firmware information is available via sysfs
# requires CONFIG_DMIID to be enabled in the Linux kernel configuration
sysfs_firmware_info = {
'biosversion': 'bios_version',
'productname': 'product_name',
'manufacturer': 'sys_vendor',
'biosreleasedate': 'bios_date',
'uuid': 'product_uuid',
'serialnumber': 'product_serial'
}
for key, fw_file in sysfs_firmware_info.items():
contents_file = os.path.join('/sys/class/dmi/id', fw_file)
if os.path.exists(contents_file):
try:
with salt.utils.fopen(contents_file, 'r') as ifile:
grains[key] = ifile.read()
if key == 'uuid':
grains['uuid'] = grains['uuid'].lower()
except (IOError, OSError) as err:
# PermissionError is new to Python 3, but corresponds to the EACESS and
# EPERM error numbers. Use those instead here for PY2 compatibility.
if err.errno == EACCES or err.errno == EPERM:
# Skip the grain if non-root user has no access to the file.
pass
elif salt.utils.which_bin(['dmidecode', 'smbios']) is not None and not (
salt.utils.is_smartos() or
( # SunOS on SPARC - 'smbios: failed to load SMBIOS: System does not export an SMBIOS table'
osdata['kernel'] == 'SunOS' and
osdata['cpuarch'].startswith('sparc')
)):
# On SmartOS (possibly SunOS also) smbios only works in the global zone
# smbios is also not compatible with linux's smbios (smbios -s = print summarized)
grains = {
'biosversion': __salt__['smbios.get']('bios-version'),
'productname': __salt__['smbios.get']('system-product-name'),
'manufacturer': __salt__['smbios.get']('system-manufacturer'),
'biosreleasedate': __salt__['smbios.get']('bios-release-date'),
'uuid': __salt__['smbios.get']('system-uuid')
}
grains = dict([(key, val) for key, val in grains.items() if val is not None])
uuid = __salt__['smbios.get']('system-uuid')
if uuid is not None:
grains['uuid'] = uuid.lower()
for serial in ('system-serial-number', 'chassis-serial-number', 'baseboard-serial-number'):
serial = __salt__['smbios.get'](serial)
if serial is not None:
grains['serialnumber'] = serial
break
elif salt.utils.which_bin(['fw_printenv']) is not None:
# ARM Linux devices expose UBOOT env variables via fw_printenv
hwdata = {
'manufacturer': 'manufacturer',
'serialnumber': 'serial#',
}
for grain_name, cmd_key in six.iteritems(hwdata):
result = __salt__['cmd.run_all']('fw_printenv {0}'.format(cmd_key))
if result['retcode'] == 0:
uboot_keyval = result['stdout'].split('=')
grains[grain_name] = _clean_value(grain_name, uboot_keyval[1])
elif osdata['kernel'] == 'FreeBSD':
# On FreeBSD /bin/kenv (already in base system)
# can be used instead of dmidecode
kenv = salt.utils.which('kenv')
if kenv:
# In theory, it will be easier to add new fields to this later
fbsd_hwdata = {
'biosversion': 'smbios.bios.version',
'manufacturer': 'smbios.system.maker',
'serialnumber': 'smbios.system.serial',
'productname': 'smbios.system.product',
'biosreleasedate': 'smbios.bios.reldate',
'uuid': 'smbios.system.uuid',
}
for key, val in six.iteritems(fbsd_hwdata):
value = __salt__['cmd.run']('{0} {1}'.format(kenv, val))
grains[key] = _clean_value(key, value)
elif osdata['kernel'] == 'OpenBSD':
sysctl = salt.utils.which('sysctl')
hwdata = {'biosversion': 'hw.version',
'manufacturer': 'hw.vendor',
'productname': 'hw.product',
'serialnumber': 'hw.serialno',
'uuid': 'hw.uuid'}
for key, oid in six.iteritems(hwdata):
value = __salt__['cmd.run']('{0} -n {1}'.format(sysctl, oid))
if not value.endswith(' value is not available'):
grains[key] = _clean_value(key, value)
elif osdata['kernel'] == 'NetBSD':
sysctl = salt.utils.which('sysctl')
nbsd_hwdata = {
'biosversion': 'machdep.dmi.board-version',
'manufacturer': 'machdep.dmi.system-vendor',
'serialnumber': 'machdep.dmi.system-serial',
'productname': 'machdep.dmi.system-product',
'biosreleasedate': 'machdep.dmi.bios-date',
'uuid': 'machdep.dmi.system-uuid',
}
for key, oid in six.iteritems(nbsd_hwdata):
result = __salt__['cmd.run_all']('{0} -n {1}'.format(sysctl, oid))
if result['retcode'] == 0:
grains[key] = _clean_value(key, result['stdout'])
elif osdata['kernel'] == 'Darwin':
grains['manufacturer'] = 'Apple Inc.'
sysctl = salt.utils.which('sysctl')
hwdata = {'productname': 'hw.model'}
for key, oid in hwdata.items():
value = __salt__['cmd.run']('{0} -b {1}'.format(sysctl, oid))
if not value.endswith(' is invalid'):
grains[key] = _clean_value(key, value)
elif osdata['kernel'] == 'SunOS' and osdata['cpuarch'].startswith('sparc'):
# Depending on the hardware model, commands can report different bits
# of information. With that said, consolidate the output from various
# commands and attempt various lookups.
data = ""
for (cmd, args) in (('/usr/sbin/prtdiag', '-v'), ('/usr/sbin/prtconf', '-vp'), ('/usr/sbin/virtinfo', '-a')):
if salt.utils.which(cmd): # Also verifies that cmd is executable
data += __salt__['cmd.run']('{0} {1}'.format(cmd, args))
data += '\n'

sn_regexes = [
re.compile(r) for r in [
r'(?im)^\s*Chassis\s+Serial\s+Number\n-+\n(\S+)', # prtdiag
r'(?im)^\s*chassis-sn:\s*(\S+)', # prtconf
r'(?im)^\s*Chassis\s+Serial#:\s*(\S+)', # virtinfo
]
]

obp_regexes = [
re.compile(r) for r in [
r'(?im)^\s*System\s+PROM\s+revisions.*\nVersion\n-+\nOBP\s+(\S+)\s+(\S+)', # prtdiag
r'(?im)^\s*version:\s*\'OBP\s+(\S+)\s+(\S+)', # prtconf
]
]

fw_regexes = [
re.compile(r) for r in [
r'(?im)^\s*Sun\s+System\s+Firmware\s+(\S+)\s+(\S+)', # prtdiag
]
]

uuid_regexes = [
re.compile(r) for r in [
r'(?im)^\s*Domain\s+UUID:\s*(\S+)', # virtinfo
]
]

manufacture_regexes = [
re.compile(r) for r in [
r'(?im)^\s*System\s+Configuration:\s*(.*)(?=sun)', # prtdiag
]
]

product_regexes = [
re.compile(r) for r in [
r'(?im)^\s*System\s+Configuration:\s*.*?sun\d\S+\s(.*)', # prtdiag
r'(?im)^\s*banner-name:\s*(.*)', # prtconf
r'(?im)^\s*product-name:\s*(.*)', # prtconf
]
]

sn_regexes = [
re.compile(r) for r in [
r'(?im)Chassis\s+Serial\s+Number\n-+\n(\S+)', # prtdiag
r'(?i)Chassis\s+Serial#:\s*(\S+)', # virtinfo
r'(?i)chassis-sn:\s*(\S+)', # prtconf
]
]

obp_regexes = [
re.compile(r) for r in [
r'(?im)System\s+PROM\s+revisions.*\nVersion\n-+\nOBP\s+(\S+)\s+(\S+)', # prtdiag
r'(?im)version:\s*\'OBP\s+(\S+)\s+(\S+)', # prtconf
]
]

fw_regexes = [
re.compile(r) for r in [
r'(?i)Sun\s+System\s+Firmware\s+(\S+)\s+(\S+)', # prtdiag
]
]

uuid_regexes = [
re.compile(r) for r in [
r'(?i)Domain\s+UUID:\s+(\S+)', # virtinfo
]
]

for regex in sn_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
grains['serialnumber'] = res.group(1).strip().replace("'", "")
break

for regex in obp_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
obp_rev, obp_date = res.groups()[0:2] # Limit the number in case we found the data in multiple places
grains['biosversion'] = obp_rev.strip().replace("'", "")
grains['biosreleasedate'] = obp_date.strip().replace("'", "")

for regex in fw_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
fw_rev, fw_date = res.groups()[0:2]
grains['systemfirmware'] = fw_rev.strip().replace("'", "")
grains['systemfirmwaredate'] = fw_date.strip().replace("'", "")
break

for regex in uuid_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
grains['uuid'] = res.group(1).strip().replace("'", "")
break

for regex in manufacture_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
grains['manufacture'] = res.group(1).strip().replace("'", "")
break

for regex in product_regexes:
res = regex.search(data)
if res and len(res.groups()) >= 1:
grains['product'] = res.group(1).strip().replace("'", "")
break

return grains


def _smartos_computenode_data():
'''
Return useful information from a SmartOS compute node
'''
# Provides:
# vms_total
# vms_running
# vms_stopped
# sdc_version
# vm_capable
# vm_hw_virt

if salt.utils.is_proxy():
return {}

grains = {}

# *_vms grains
grains['computenode_vms_total'] = len(__salt__['cmd.run']('vmadm list -p').split("\n"))
grains['computenode_vms_running'] = len(__salt__['cmd.run']('vmadm list -p state=running').split("\n"))
grains['computenode_vms_stopped'] = len(__salt__['cmd.run']('vmadm list -p state=stopped').split("\n"))

# sysinfo derived grains
sysinfo = json.loads(__salt__['cmd.run']('sysinfo'))
grains['computenode_sdc_version'] = sysinfo['SDC Version']
grains['computenode_vm_capable'] = sysinfo['VM Capable']
if sysinfo['VM Capable']:
grains['computenode_vm_hw_virt'] = sysinfo['CPU Virtualization']

# sysinfo derived smbios grains
grains['manufacturer'] = sysinfo['Manufacturer']
grains['productname'] = sysinfo['Product']
grains['uuid'] = sysinfo['UUID']

return grains


def _smartos_zone_data():
'''
Return useful information from a SmartOS zone
'''
# Provides:
# pkgsrcversion
# imageversion
# pkgsrcpath
# zonename
# zoneid
# hypervisor_uuid
# datacenter

if salt.utils.is_proxy():
return {}

grains = {}

pkgsrcversion = re.compile('^release:\\s(.+)')
imageversion = re.compile('Image:\\s(.+)')
pkgsrcpath = re.compile('PKG_PATH=(.+)')
if os.path.isfile('/etc/pkgsrc_version'):
with salt.utils.fopen('/etc/pkgsrc_version', 'r') as fp_:
for line in fp_:
match = pkgsrcversion.match(line)
if match:
grains['pkgsrcversion'] = match.group(1)
if os.path.isfile('/etc/product'):
with salt.utils.fopen('/etc/product', 'r') as fp_:
for line in fp_:
match = imageversion.match(line)
if match:
grains['imageversion'] = match.group(1)
if os.path.isfile('/opt/local/etc/pkg_install.conf'):
with salt.utils.fopen('/opt/local/etc/pkg_install.conf', 'r') as fp_:
for line in fp_:
match = pkgsrcpath.match(line)
if match:
grains['pkgsrcpath'] = match.group(1)
if 'pkgsrcversion' not in grains:
grains['pkgsrcversion'] = 'Unknown'
if 'imageversion' not in grains:
grains['imageversion'] = 'Unknown'
if 'pkgsrcpath' not in grains:
grains['pkgsrcpath'] = 'Unknown'

grains['zonename'] = __salt__['cmd.run']('zonename')
grains['zoneid'] = __salt__['cmd.run']('zoneadm list -p | awk -F: \'{ print $1 }\'', python_shell=True)

return grains


def _zpool_data(grains):
'''
Provide grains about zpools
'''
# quickly return if windows or proxy
if salt.utils.is_windows() or 'proxyminion' in __opts__:
return {}

# quickly return if no zpool and zfs command
if not salt.utils.which('zpool'):
return {}

# collect zpool data
zpool_grains = {}
for zpool in __salt__['cmd.run']('zpool list -H -o name,size').splitlines():
zpool = zpool.split()
zpool_grains[zpool[0]] = zpool[1]

# return grain data
if len(zpool_grains.keys()) < 1:
return {}
return {'zpool': zpool_grains}


def get_server_id():
'''
Provides an integer based on the FQDN of a machine.
Useful as server-id in MySQL replication or anywhere else you'll need an ID
like this.
'''
# Provides:
# server_id

if salt.utils.is_proxy():
return {}
return {'server_id': abs(hash(__opts__.get('id', '')) % (2 ** 31))}


def get_master():
'''
Provides the minion with the name of its master.
This is useful in states to target other services running on the master.
'''
# Provides:
# master
return {'master': __opts__.get('master', '')}

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

posted on 2017-03-01 10:52  阿里山QQ  阅读(984)  评论(0编辑  收藏  举报