├── .gitignore
├── CHANGES.txt
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── README.txt
├── bin
├── pysa
└── pysa2puppet
├── examples
├── .gitignore
├── filters.addition
├── filters.discard
├── filters.replace
├── filters.update
└── pysa.cfg
├── pysa.py
├── pysa
├── __init__.py
├── config.py
├── dependencies.py
├── exception.py
├── filter
│ ├── __init__.py
│ ├── filter.py
│ └── parser.py
├── madeira.py
├── output.py
├── preprocessing.py
├── puppet
│ ├── __init__.py
│ ├── build.py
│ ├── converter.py
│ └── objects.py
├── salt
│ ├── __init__.py
│ ├── build.py
│ ├── converter.py
│ └── objects.py
├── scanner
│ ├── __init__.py
│ ├── actions
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cron.py
│ │ ├── file.py
│ │ ├── gem.py
│ │ ├── group.py
│ │ ├── host.py
│ │ ├── mount.py
│ │ ├── npm.py
│ │ ├── package.py
│ │ ├── php.py
│ │ ├── process.py
│ │ ├── pypi.py
│ │ ├── repository.py
│ │ ├── service.py
│ │ ├── source.py
│ │ ├── sshkey.py
│ │ ├── user.py
│ │ └── utils.py
│ ├── object
│ │ ├── __init__.py
│ │ ├── cron.py
│ │ ├── file.py
│ │ ├── group.py
│ │ ├── host.py
│ │ ├── mount.py
│ │ ├── object_base.py
│ │ ├── package.py
│ │ ├── process.py
│ │ ├── repository.py
│ │ ├── service.py
│ │ ├── source.py
│ │ ├── sshkey.py
│ │ └── user.py
│ └── scanner_handler.py
└── tools.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/.gitignore
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | v0.3bx, 2013.12.20 -- general fixes
2 | v0.3b, 2013.10.15 -- Beta1 (0.3) release
3 | v0.2.5a, 2013.7.24 -- SaltStack module improvements and bug fixes
4 | v0.2.4a, 2013.7.23 -- Introducing SaltStack module, typo improvement, bug fixes.
5 | v0.2.3ax, 2013.7.xx -- Bug fixes
6 | v0.2.3a, 2013.6.29 -- Improvment on puppet module (readability mostly)
7 | v0.2.2ax, 2013.6.13 -- Minor alpha fix are not written in docs anymore (0.2.2ax -> 0.2.2a)
8 | v0.2.2a1, 2013.6.13 -- Add manpage try (not functionnal)
9 | v0.2.2a, 2013.6.13 -- Config file addition + Blueprint comparison (doc)
10 | v0.2.1a4, 2013.6.11 -- Alpha release - Doc fix
11 | v0.2.1a3, 2013.6.11 -- Alpha release - ssh key fix
12 | v0.2.1a2, 2013.6.10 -- Alpha release - Doc fix
13 | v0.2.1a1, 2013.6.10 -- Alpha release - Doc fix
14 | v0.2.1a, 2013.6.09 -- Alpha release - Install issues fix - First installable version (please don't use any previous version).
15 | v0.2.0ax, 2013.6.09 -- Fixes and tests setup script.
16 | v0.2.0a, 2013.6.07 -- Alpha release. Includes dependencies improvment, pre-alpha fixes and replication helper.
17 | v0.1.xax, 2013.6.xx -- Pre-alpha fix, doc formating.
18 | v0.1a, 2013.5.24 -- Initial release.
19 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.txt
2 | include examples/*
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Welcome to Pysa project page
2 |
3 | Pysa is a configuration reverse engineering software, which is aimed to help anyone who wants to replicate an existing computer configuration - and not simply clone the entire machine. It can be used to migrate configurations from one computer to another (including physical machines to virtual Clouds), backup existing configurations, or for any other migration purpose.
4 |
5 | ## Install
6 |
7 | Pysa has been designed for UNIX Operating Systems. However, only Linux based distributions are supported for now (tested on Ubuntu, Debian, Cent OS, RedHat and Amazon Linux latest and LTS versions). More will come later.
8 |
9 | There are two ways to install Pysa: using the sources files or using the PyPI packages repository.
10 |
11 | ### Using PyPI (recommended)
12 |
13 | - Ensure pip is setup on your computer (you can alternatively use other PyPI managers, like easy_install)
14 | - Setup Pysa in its latest version pip install pysa
15 | - Run Pysa (see documentation)
16 |
17 | ### Sources setup
18 |
19 | - Clone this git repo `git clone https://github.com/MadeiraCloud/pysa.git`
20 | - Run Pysa (pysa.py - see documentation)
21 |
22 | ## Documentation
23 |
24 | You can access the documentation at http://madeira-cloud-pysa-document.readthedocs.org/en/latest/
25 |
26 | ## Authors and Contributors
27 |
28 | - Thibault BRONCHAIN thibault {at} mc2 {dot} io
29 | - Michael CHO michael {at} mc2 {dot} io
30 | - Ken CHEN ken {at} mc2 {dot} io
31 |
32 | ## Support or Contact
33 |
34 | Having trouble, remarks or suggestions about Pysa? Contact us at pysa@mc2.io and we'll help you sort it out.
35 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Welcome to Pysa project page
2 | ----------------------------
3 |
4 | Pysa is a configuration reverse engineering software, which is aimed to
5 | help anyone who wants to replicate an existing computer configuration -
6 | and not simply clone the entire machine. It can be used to migrate
7 | configurations from one computer to another (including physical machines
8 | to virtual Clouds), backup existing configurations, or for any other
9 | migration purpose.
10 |
11 | Install
12 | -------
13 |
14 | Pysa has been designed for UNIX Operating Systems. However, only Linux
15 | based distributions are supported for now (tested on Ubuntu, Debian,
16 | Cent OS, RedHat and Amazon Linux latest and LTS versions). More will
17 | come later.
18 |
19 | There are two ways to install Pysa: using the sources files or using the
20 | PyPI packages repository.
21 |
22 | Using PyPI (recommended)
23 | ~~~~~~~~~~~~~~~~~~~~~~~~
24 |
25 | - Ensure pip is setup on your computer (you can alternatively use other
26 | PyPI managers, like easy\_install)
27 | - Setup Pysa in its latest version pip install pysa
28 | - Run Pysa (see documentation)
29 |
30 | Sources setup
31 | ~~~~~~~~~~~~~
32 |
33 | - Clone this git repo
34 | ``git clone https://github.com/MadeiraCloud/pysa.git``
35 | - Run Pysa (pysa.py - see documentation)
36 |
37 | Documentation
38 | -------------
39 |
40 | You can access the documentation at
41 | http://madeira-cloud-pysa-document.readthedocs.org/en/latest/
42 |
43 | Authors and Contributors
44 | ------------------------
45 |
46 | - Thibault BRONCHAIN thibault {at} mc2 {dot} io
47 | - Michael CHO michael {at} mc2 {dot} io
48 | - Ken CHEN ken {at} mc2 {dot} io
49 |
50 | Support or Contact
51 | ------------------
52 |
53 | Having trouble, remarks or suggestions about Pysa? Contact us at
54 | pysa@mc2.io and we'll help you sort it out.
55 |
--------------------------------------------------------------------------------
/bin/pysa:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | '''
3 | Main file
4 |
5 | pysa - reverse a complete computer setup
6 | Copyright (C) 2013 MadeiraCloud Ltd.
7 |
8 | This program is free software: you can redistribute it and/or modify
9 | it under the terms of the GNU General Public License as published by
10 | the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see .
20 |
21 | @author: Thibault BRONCHAIN
22 | '''
23 |
24 | from optparse import *
25 | import logging
26 | import re
27 |
28 | from pysa.tools import Tools
29 | from pysa.preprocessing import Preprocessing
30 | from pysa.config import *
31 | from pysa.exception import *
32 | from pysa.madeira import *
33 |
34 | from pysa.filter.parser import FParser
35 | from pysa.scanner.scanner_handler import module_scan
36 |
37 | from pysa.puppet.converter import PuppetConverter
38 | from pysa.puppet.build import PuppetBuild
39 |
40 | from pysa.salt.converter import SaltConverter
41 | from pysa.salt.build import SaltBuild
42 |
43 |
44 | # global defines
45 | USAGE = 'usage: %prog [-hqps] [-m module_name] [-o output_path] [-c config_file_path] [-f filter_config_path] [-l {-u madeira_username}|{-i madeira_id}]'
46 | VERSION_NBR = '0.3b'
47 | VERSION = '%prog '+VERSION_NBR
48 |
49 | # logger settings
50 | LOG_FILENAME = '/tmp/scanner.log'
51 | LOG_FORMAT = '%(asctime)s-%(name)s-%(levelname)s-%(message)s'
52 | def __log(lvl):
53 | level = logging.getLevelName(lvl)
54 | formatter = logging.Formatter(LOG_FORMAT)
55 | handler = logging.StreamHandler()
56 | logger = logging.getLogger()
57 | handler.setFormatter(formatter)
58 | logger.setLevel(level)
59 | logger.addHandler(handler)
60 |
61 |
62 | # scanner class
63 | class Scanner():
64 | def __init__(self, filters=None):
65 | self.resources = None
66 | self.filters = filters
67 | self.preprocessed = None
68 |
69 | @GeneralException
70 | # get resource from different modules
71 | def scan(self):
72 | logging.info('Scanner.scan(): start scanning')
73 | self.resources = module_scan(self.filters if self.filters else None)
74 |
75 | @GeneralException
76 | # generate puppet files
77 | def preprocessing(self, module):
78 | if not self.resources:
79 | logging.error('Scanner.preprocessing(): No resources')
80 | return
81 | logging.info('Scanner.preprocessing(): Running')
82 | return Preprocessing(module).run(self.resources)
83 |
84 | @GeneralException
85 | # generate puppet files
86 | def show_puppet(self, path, module):
87 | # if not self.preprocessed:
88 | # logging.error('Scanner.show_puppet(): No data')
89 | # return
90 | logging.info('Scanner.show_puppet(): Puppet files will be stored in path: %s' % path)
91 | puppetdict = PuppetConverter(self.preprocessing('pysa.puppet'), self.filters)
92 | p = puppetdict.run()
93 | puppet = PuppetBuild(p, path, module)
94 | puppet.run()
95 |
96 | @GeneralException
97 | # generate salt files
98 | def show_salt(self, path, module):
99 | # if not self.preprocessed:
100 | # logging.error('Scanner.show_salt(): No data')
101 | # return
102 | logging.info('Scanner.show_salt(): Salt files will be stored in path: %s' % path)
103 | saltdict = SaltConverter(self.preprocessing('pysa.salt'), self.filters)
104 | s = saltdict.run()
105 | salt = SaltBuild(s, path, module)
106 | salt.run()
107 |
108 | # print header
109 | def print_header():
110 | print "Pysa v"+VERSION_NBR
111 | print '''
112 |
113 | pysa - reverse a complete computer setup
114 | Copyright (C) 2013 MadeiraCloud Ltd.
115 |
116 | Thank you for using pysa!
117 | Be aware that you are using an early-build (alpha release).
118 | To provide the best result, ensure that you are not using an outdated version (check out http://github.com/MadeiraCloud/pysa or http://pypi.python.org/pypi/Pysa to get the latest version).
119 | Please don't hesitate to report any bugs, requirements, advice, criticisms, hate or love messages to either pysa-user@googlegroups.com for public discussions and pysa@mc2.io for private messages.
120 | '''
121 |
122 | # option parser - user handler
123 | def check_user(option, opt_str, value, parser):
124 | if parser.values.user or parser.values.id:
125 | setattr(parser.values, option.dest, True)
126 | else:
127 | raise OptionValueError("can't use -l without -u or -i (see usage)")
128 |
129 | # option parser
130 | def main_parse():
131 | parser = OptionParser(usage=USAGE, version=VERSION)
132 | parser.add_option("-c", "--config", action="store", dest="config",
133 | help="specify config file"
134 | )
135 | parser.add_option("-p", "--puppet", action="store_true", dest="puppet", default=False,
136 | help="scan packages and generate the puppet manifests"
137 | )
138 | parser.add_option("-s", "--salt", action="store_true", dest="salt", default=False,
139 | help="[EXPERIMENTAL] scan packages and generate the salt manifests"
140 | )
141 | parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False,
142 | help="operate quietly"
143 | )
144 | parser.add_option("-m", "--module", action="store", dest="module", default="pysa",
145 | help="define module name"
146 | )
147 | parser.add_option("-o", "--output", action="store", dest="output", default="./output",
148 | help="Path to output"
149 | )
150 | parser.add_option("-f", "--filter", action="store", dest="filter",
151 | help="add some user filters"
152 | )
153 | parser.add_option("-l", "--madeira", action="callback", callback=check_user, dest='l',
154 | help="post data to madeira"
155 | )
156 | parser.add_option("-u", "--user", "--username", action="store", dest='user',
157 | help="madeira username"
158 | )
159 | parser.add_option("-i", "--id", action="store", dest='id',
160 | help="identify user id"
161 | )
162 | return parser.parse_args()
163 |
164 | def main():
165 | # print header
166 | print_header()
167 |
168 | # options parsing
169 | options, args = main_parse()
170 | __log(('ERROR' if options.quiet else 'INFO'))
171 | output = (options.output if options.output else "./output")
172 | module = (options.module if options.module else "pysa")
173 | user = (options.user if options.user else None)
174 | uid = (options.id if options.id else None)
175 |
176 | # config parser
177 | Config(options.config if options.config else None)
178 |
179 | # filters parsing
180 | filter_parser = FParser(options.filter if options.filter else None)
181 | filters = filter_parser.run()
182 |
183 | # scan for files
184 | s = Scanner(filters)
185 | s.scan()
186 | # generate puppet output
187 | if options.puppet:
188 | s.show_puppet(output, module)
189 | # generate salt output
190 | if options.salt:
191 | s.show_salt(output, module)
192 |
193 | # save to madeira accound
194 | if options.l:
195 | m = Madeira(user, uid, output, module)
196 | m.send()
197 |
198 |
199 | if __name__ == '__main__':
200 | main()
201 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 |
--------------------------------------------------------------------------------
/examples/filters.addition:
--------------------------------------------------------------------------------
1 | # Part of pysa utility
2 | #
3 | # Filtering file example
4 | # Please see pysa man page for section description
5 | #
6 |
7 | [addition.source]
8 | # set global user/password for all sources
9 | # def: xxx = xxx
10 | ownner = root
11 | password = whatever
12 |
13 | [addition.source.name.myproject]
14 | # add devops source's password
15 | # def: xxx = xxx
16 | ownner = root
17 | password = secure-password
18 |
19 | [addition.source.scm.hg]
20 | # change the mode of all the hg source
21 | # def: xxx = xxx
22 | mode = 0744
23 |
24 | [addition.package.name.zip]
25 | # change package's architecture
26 | # def: xxx = xxx
27 | platform = i386
28 |
29 | [addition.user.name.imauser]
30 | # change user's shell
31 | # def: xxx = xxx
32 | shell = /bin/sh
33 |
34 | [addition.service.name.imaservice]
35 | # change service's boot status
36 | # def: xxx = xxx
37 | ensure = False
38 |
39 | [addition.file.path./etc/test.conf]
40 | # change file's path
41 | # def: xxx = xxx
42 | path = /etc/test_1.conf
43 |
--------------------------------------------------------------------------------
/examples/filters.discard:
--------------------------------------------------------------------------------
1 | # Part of pysa utility
2 | #
3 | # Filtering file example
4 | # Please see pysa man page for section description
5 | #
6 |
7 | [discard]
8 | # use it carefully, some resources might depend on others
9 | _resources = package, cron, group, mount, repository, service, key, user, process, gem, npm, php, pypi
10 |
11 | # package
12 | # def: package.xxx = xxx
13 | package.name = subversion, mercurial
14 | package.platform = x86_64, amd64
15 |
16 | # file
17 | # def: file.xxx = xxx
18 | file.path = /etc/host.conf
19 |
20 | # cron
21 | # def: cron.xxx = xxx
22 |
23 | # group
24 | # def: group.xxx = xxx
25 |
26 | # mount
27 | # def: mount.xxx = xxx
28 |
29 | # repo
30 | # def: repository.xxx = xxx
31 |
32 | # service
33 | # def: service.xxx = xxx
34 | service.provider = upstart
35 |
36 | # key
37 | # def: key.xxx = xxx
38 |
39 | # user
40 | # def: user.xxx = xxx
41 | user.name = mc
42 |
43 | # process
44 | # def: process.xxx = xxx
45 |
46 | # source
47 | # def: source.xxx = xxx
48 | source.name = devops
49 | source.source = 211.98.26.6
50 |
--------------------------------------------------------------------------------
/examples/filters.replace:
--------------------------------------------------------------------------------
1 | # Part of pysa utility
2 | #
3 | # Filtering file example
4 | # Please see pysa man page for section description
5 | #
6 |
7 | # global item replacment
8 | [replace]
9 |
10 | # set filtering mode
11 | # def: _replaceall = true/false
12 | # required
13 | _replaceall = true
14 |
15 | # list replacment values
16 | # def: new_value = old_value(s)
17 | 127.0.1.1 = 127.0.0.1
18 | myhost.com = localdomain
19 |
20 |
21 | # specific item replacment
22 | [replace.files.content]
23 |
24 | # set filtering mode
25 | # def: _replaceall = true/false
26 | # required
27 | _replaceall = false
28 |
29 | # list exception(s) (use object primary key, see man(8) pysa)
30 | # def: _except = key1, ...
31 | _except = /etc/foo.conf
32 |
33 | # list replacment values
34 | # def: new_value = old_value(s)
35 | 192.168.0.12 = 192.168.12.34
36 | myolddomain.com = mynewdomain.com
37 | toto = tata, fwfw
38 |
39 | [replace.repos.content]
40 | # refer to previous section (duplicate content)
41 | # def: _contentrefer = section
42 | _contentrefer = replace.files.content
43 |
--------------------------------------------------------------------------------
/examples/filters.update:
--------------------------------------------------------------------------------
1 | # Part of pysa utility
2 | #
3 | # Filtering file example
4 | # Please see pysa man page for section description
5 | #
6 |
7 | [update]
8 | # set filtering mode
9 | # def: _update = true/false
10 | # required
11 | _update = false
12 |
13 | # list exception(s) (use package name, see man(8) pysa)
14 | # def: except = package1, ...
15 | except = selinux*, libipa_hbac-python, kernel-firmware, libsss_idmap, sssd-client, perf
16 |
--------------------------------------------------------------------------------
/examples/pysa.cfg:
--------------------------------------------------------------------------------
1 | # Part of pysa utility
2 | #
3 | # Config file example
4 | # Please see pysa help for details
5 | #
6 |
7 | # files section
8 | [files]
9 | # set search path
10 | # def: path = xxx:xxx
11 | path = /etc:/root/.ssh
12 |
13 | # keys section
14 | [keys]
15 | # set search path
16 | # def: path = xxx:xxx
17 | path = /root/.ssh
18 |
19 | # hosts section
20 | [hosts]
21 | # set hosts file path
22 | # def: path = xxx
23 | path = /etc/hosts
24 |
25 | # package managers
26 | [managers]
27 | # define yum/apt packages for other managers
28 | # def: _autoadd = True/False
29 | _autoadd = True
30 | # def: manager_name = manager_package
31 | pear = php-pear
32 | pecl = php-pear
33 | pip = python-pip
34 | npm = npm
35 | gem = rubygems
36 |
--------------------------------------------------------------------------------
/pysa.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | '''
3 | Main file
4 |
5 | pysa - reverse a complete computer setup
6 | Copyright (C) 2013 MadeiraCloud Ltd.
7 |
8 | This program is free software: you can redistribute it and/or modify
9 | it under the terms of the GNU General Public License as published by
10 | the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see .
20 |
21 | @author: Thibault BRONCHAIN
22 | '''
23 |
24 | from optparse import *
25 | import logging
26 | import re
27 |
28 | from pysa.tools import Tools
29 | from pysa.preprocessing import Preprocessing
30 | from pysa.config import *
31 | from pysa.exception import *
32 | from pysa.madeira import *
33 |
34 | from pysa.filter.parser import FParser
35 | from pysa.scanner.scanner_handler import module_scan
36 |
37 | from pysa.puppet.converter import PuppetConverter
38 | from pysa.puppet.build import PuppetBuild
39 |
40 | from pysa.salt.converter import SaltConverter
41 | from pysa.salt.build import SaltBuild
42 |
43 |
44 | # global defines
45 | USAGE = 'usage: %prog [-hqps] [-m module_name] [-o output_path] [-c config_file_path] [-f filter_config_path] [-l {-u madeira_username}|{-i madeira_id}]'
46 | VERSION_NBR = '0.3b'
47 | VERSION = '%prog '+VERSION_NBR
48 |
49 | # logger settings
50 | LOG_FILENAME = '/tmp/scanner.log'
51 | LOG_FORMAT = '%(asctime)s-%(name)s-%(levelname)s-%(message)s'
52 | def __log(lvl):
53 | level = logging.getLevelName(lvl)
54 | formatter = logging.Formatter(LOG_FORMAT)
55 | handler = logging.StreamHandler()
56 | logger = logging.getLogger()
57 | handler.setFormatter(formatter)
58 | logger.setLevel(level)
59 | logger.addHandler(handler)
60 |
61 |
62 | # scanner class
63 | class Scanner():
64 | def __init__(self, filters=None):
65 | self.resources = None
66 | self.filters = filters
67 | self.preprocessed = None
68 |
69 | @GeneralException
70 | # get resource from different modules
71 | def scan(self):
72 | logging.info('Scanner.scan(): start scanning')
73 | self.resources = module_scan(self.filters if self.filters else None)
74 |
75 | @GeneralException
76 | # generate puppet files
77 | def preprocessing(self, module):
78 | if not self.resources:
79 | logging.error('Scanner.preprocessing(): No resources')
80 | return
81 | logging.info('Scanner.preprocessing(): Running')
82 | return Preprocessing(module).run(self.resources)
83 |
84 | @GeneralException
85 | # generate puppet files
86 | def show_puppet(self, path, module):
87 | # if not self.preprocessed:
88 | # logging.error('Scanner.show_puppet(): No data')
89 | # return
90 | logging.info('Scanner.show_puppet(): Puppet files will be stored in path: %s' % path)
91 | puppetdict = PuppetConverter(self.preprocessing('pysa.puppet'), self.filters)
92 | p = puppetdict.run()
93 | puppet = PuppetBuild(p, path, module)
94 | puppet.run()
95 |
96 | @GeneralException
97 | # generate salt files
98 | def show_salt(self, path, module):
99 | # if not self.preprocessed:
100 | # logging.error('Scanner.show_salt(): No data')
101 | # return
102 | logging.info('Scanner.show_salt(): Salt files will be stored in path: %s' % path)
103 | saltdict = SaltConverter(self.preprocessing('pysa.salt'), self.filters)
104 | s = saltdict.run()
105 | salt = SaltBuild(s, path, module)
106 | salt.run()
107 |
108 | # print header
109 | def print_header():
110 | print "Pysa v"+VERSION_NBR
111 | print '''
112 |
113 | pysa - reverse a complete computer setup
114 | Copyright (C) 2013 MadeiraCloud Ltd.
115 |
116 | Thank you for using pysa!
117 | Be aware that you are using an early-build (alpha release).
118 | To provide the best result, ensure that you are not using an outdated version (check out http://github.com/MadeiraCloud/pysa or http://pypi.python.org/pypi/Pysa to get the latest version).
119 | Please don't hesitate to report any bugs, requirements, advice, criticisms, hate or love messages to either pysa-user@googlegroups.com for public discussions and pysa@mc2.io for private messages.
120 | '''
121 |
122 | # option parser - user handler
123 | def check_user(option, opt_str, value, parser):
124 | if parser.values.user or parser.values.id:
125 | setattr(parser.values, option.dest, True)
126 | else:
127 | raise OptionValueError("can't use -l without -u or -i (see usage)")
128 |
129 | # option parser
130 | def main_parse():
131 | parser = OptionParser(usage=USAGE, version=VERSION)
132 | parser.add_option("-c", "--config", action="store", dest="config",
133 | help="specify config file"
134 | )
135 | parser.add_option("-p", "--puppet", action="store_true", dest="puppet", default=False,
136 | help="scan packages and generate the puppet manifests"
137 | )
138 | parser.add_option("-s", "--salt", action="store_true", dest="salt", default=False,
139 | help="[EXPERIMENTAL] scan packages and generate the salt manifests"
140 | )
141 | parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False,
142 | help="operate quietly"
143 | )
144 | parser.add_option("-m", "--module", action="store", dest="module", default="pysa",
145 | help="define module name"
146 | )
147 | parser.add_option("-o", "--output", action="store", dest="output", default="./output",
148 | help="Path to output"
149 | )
150 | parser.add_option("-f", "--filter", action="store", dest="filter",
151 | help="add some user filters"
152 | )
153 | parser.add_option("-l", "--madeira", action="callback", callback=check_user, dest='l',
154 | help="post data to madeira"
155 | )
156 | parser.add_option("-u", "--user", "--username", action="store", dest='user',
157 | help="madeira username"
158 | )
159 | parser.add_option("-i", "--id", action="store", dest='id',
160 | help="identify user id"
161 | )
162 | return parser.parse_args()
163 |
164 | def main():
165 | # print header
166 | print_header()
167 |
168 | # options parsing
169 | options, args = main_parse()
170 | __log(('ERROR' if options.quiet else 'INFO'))
171 | output = (options.output if options.output else "./output")
172 | module = (options.module if options.module else "pysa")
173 | user = (options.user if options.user else None)
174 | uid = (options.id if options.id else None)
175 |
176 | # config parser
177 | Config(options.config if options.config else None)
178 |
179 | # filters parsing
180 | filter_parser = FParser(options.filter if options.filter else None)
181 | filters = filter_parser.run()
182 |
183 | # scan for files
184 | s = Scanner(filters)
185 | s.scan()
186 | # generate puppet output
187 | if options.puppet:
188 | s.show_puppet(output, module)
189 | # generate salt output
190 | if options.salt:
191 | s.show_salt(output, module)
192 |
193 | # save to madeira accound
194 | if options.l:
195 | m = Madeira(user, uid, output, module)
196 | m.send()
197 |
198 |
199 | if __name__ == '__main__':
200 | main()
201 |
--------------------------------------------------------------------------------
/pysa/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/__init__.py
--------------------------------------------------------------------------------
/pysa/config.py:
--------------------------------------------------------------------------------
1 | '''
2 | Global configuration
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from ConfigParser import SafeConfigParser
24 |
25 | from pysa.tools import *
26 | from pysa.exception import *
27 |
28 |
29 | # define who is a file
30 | FILE_CLASS = ['keys', 'repos', 'files']
31 | # order
32 | ORDER_LIST = [
33 | 'hosts',
34 | 'mounts',
35 | 'groups',
36 | 'users',
37 | 'dirs',
38 | 'keys',
39 | 'repos',
40 | 'packages',
41 | 'files',
42 | 'crons',
43 | 'sources',
44 | 'services',
45 | ]
46 | # null objects (avoid 0)
47 | NULL = ['', {}, [], None]
48 | # build-ins
49 | VOID_EQ = '_'
50 | ACTION_ID = '_'
51 | MAIN_SECTION = '_'
52 | SINGLE_SEC = '__'
53 |
54 | # configuration class
55 | class Config():
56 |
57 | # default values
58 | c = {
59 | 'files' : {
60 | 'path' : '/etc:/root/.ssh'
61 | },
62 | 'keys' : {
63 | 'path' : 'root/.ssh'
64 | },
65 | 'hosts' : {
66 | 'path' : '/etc/hosts'
67 | },
68 | 'managers' : {
69 | '_autoadd' : True,
70 | 'pear' : 'php-pear',
71 | 'pecl' : 'php-pear',
72 | 'pip' : 'python-pip',
73 | 'npm' : 'npm',
74 | 'gem' : 'rubygems',
75 | },
76 | }
77 | files_path = c['files']['path']
78 | scan_host = c['hosts']['path']
79 | key_path = c['keys']['path']
80 | managers_eq = c['managers']
81 | platform = None
82 |
83 | # edit default values if config file
84 | def __init__(self, path=None):
85 | if not path: return
86 | self.__filename = path
87 | self.__parse_config()
88 |
89 | # parse config file
90 | @GeneralException
91 | def __parse_config(self):
92 | parser = SafeConfigParser()
93 | parser.read(self.__filename)
94 | for name in parser.sections():
95 | config.c.setdefault(name, {})
96 | for key, value in parser.items(name):
97 | if value == "True" : value = True
98 | elif value == "False": value = False
99 | config.c[name][key] = value
100 |
--------------------------------------------------------------------------------
/pysa/dependencies.py:
--------------------------------------------------------------------------------
1 | '''
2 | List dependencies for all resources
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 |
24 | import re
25 | import copy
26 |
27 | from pysa.tools import *
28 | from pysa.config import *
29 | from pysa.exception import *
30 |
31 |
32 | SCM_EQ = {
33 | 'git' : ['git', 'git-all'],
34 | 'svn' : ['subversion'],
35 | 'hg' : ['mercurial'],
36 | }
37 |
38 | SELF_ORDER_EQ = {
39 | 'dirs' : 'path',
40 | 'sources' : 'path',
41 | 'mounts' : 'name',
42 | }
43 |
44 | PRIOR = ['sources']
45 |
46 | BASED_ON_FIELD = "BASED_ON_FIELD"
47 | SELF_ORDER = "SELF_ORDER"
48 | GET_MOUNT_FROM_PATH = "GET_MOUNT_FROM_PATH"
49 | GET_BASE_PATH = "GET_BASE_PATH"
50 | GET_PKG_FROM_SCM = "GET_PKG_FROM_SCM"
51 | PACKAGE_MANAGER = "PACKAGE_MANAGER"
52 |
53 |
54 | class Dependencies:
55 | def __init__(self, module):
56 | exec "from %s.converter import SECTION_CALL_EQ"%module
57 | exec "from %s.objects import *"%module
58 | self.__obj_maker = OBJ_MAKER
59 | self.__deps = DEPENDENCIES
60 | self.__calls = SECTION_CALL_EQ
61 | self.__handler = {
62 | BASED_ON_FIELD : self.__based_on_field,
63 | SELF_ORDER : self.__self_order,
64 | GET_MOUNT_FROM_PATH : self.__get_mount_from_path,
65 | GET_BASE_PATH : self.__get_base_path,
66 | GET_PKG_FROM_SCM : self.__get_pkg_from_scm,
67 | PACKAGE_MANAGER : self.__package_manager,
68 | }
69 | self.__data = None
70 | self.__add_obj = {}
71 |
72 | @GeneralException
73 | def run(self, data):
74 | self.__data = copy.deepcopy(data)
75 | Tools.l(INFO, "running dependency cycle generation", 'run', self)
76 | for c in self.__data:
77 | if ((c not in self.__deps)
78 | or (c not in self.__calls)): continue
79 | for obj_name in self.__data[c]:
80 | obj = self.__data[c][obj_name]
81 | for dep_name in PRIOR:
82 | if ((dep_name not in self.__data)
83 | or (dep_name not in self.__calls)): continue
84 | elif dep_name in self.__deps[c]:
85 | self.__parse_dep(c, obj_name, obj, dep_name)
86 | for dep_name in self.__deps[c]:
87 | if ((dep_name not in self.__data)
88 | or (dep_name not in self.__calls)
89 | or (dep_name in PRIOR)): continue
90 | else: self.__parse_dep(c, obj_name, obj, dep_name)
91 | if self.__add_obj: self.__data = Tools.dict_merging(self.__add_obj, self.__data)
92 | Tools.l(INFO, "dependency cycle generated", 'run', self)
93 | return self.__data
94 |
95 | @GeneralException
96 | def __parse_dep(self, c, obj_name, obj, dep_name):
97 | dep = self.__deps[c][dep_name]
98 | if type(dep) is str:
99 | obj['require'] = Tools.dict_merging(obj.get('require'), {
100 | dep : [dep_name]
101 | })
102 | elif type(dep) is list:
103 | res = self.__handler[dep[0]](obj, dep_name, dep[1])
104 | if res:
105 | section_dep = self.__calls[dep_name]
106 | if type(self.__calls[dep_name]) is dict:
107 | target_obj = (res[0] if type(res) is list else res)
108 | tmp_data = (Tools.dict_merging(self.__add_obj, self.__data) if self.__add_obj else self.__data)
109 | section_obj = tmp_data[dep_name].get(target_obj)
110 | if not section_obj:
111 | Tools.l(ERR, "Target object missing %s.%s"%(dep_name,target_obj), 'parse_dep', self)
112 | return
113 | section_key = section_obj.get(self.__calls[dep_name]['key'])
114 | if not section_key:
115 | Tools.l(ERR, "Section key missing for %s"%(dep_name), 'parse_dep', self)
116 | return
117 | section_dep = self.__calls[dep_name].get(section_key)
118 | if not section_dep:
119 | Tools.l(ERR, "Wrong section key %s[%s]"%(dep_name,section_key), 'parse_dep', self)
120 | return
121 | obj['require'] = Tools.dict_merging(obj.get('require'), {
122 | section_dep[len(ACTION_ID):] : res
123 | })
124 |
125 | @GeneralException
126 | def __self_order(self, object, gclass, args):
127 | ref = object.get(args['field'])
128 | data = self.__data[gclass]
129 | if not data: return None
130 | res = None
131 | for key in sorted(data, key=lambda x: data[x][args['field']]):
132 | name = data[key][args['field']]
133 | if re.match(name, ref) and (ref != name):
134 | res = key
135 | if gclass == 'sources' and self.__data.get('dirs') and res:
136 | dirs = dict(self.__data['dirs'].items())
137 | for dir in dirs:
138 | comp = Tools.path_basename(ref)
139 | if dirs[dir]['path'] == comp:
140 | self.__data['dirs'].pop(dir)
141 | return res
142 |
143 | @GeneralException
144 | def __get_mount_from_path(self, object, gclass, args):
145 | path = object.get(args['field'])
146 | mounts = self.__data['mounts']
147 | if not mounts: return None
148 | res = None
149 | for key in sorted(mounts, key=lambda x: mounts[x]['name']):
150 | name = mounts[key]['name']
151 | if re.match(name, path):
152 | res = key
153 | return res
154 |
155 | @GeneralException
156 | def __get_pkg_from_scm(self, object, gclass, args):
157 | scm = object.get(args['field'])
158 | return (SCM_EQ.get(scm) if scm else None)
159 |
160 | @GeneralException
161 | def __get_base_path(self, object, gclass, args):
162 | path = object.get(args['field'])
163 | return (Tools.path_basename(path) if path else None)
164 |
165 | @GeneralException
166 | def __based_on_field(self, object, gclass, args):
167 | if not args.get('field') or not args.get('key'):
168 | return []
169 | res = []
170 | v_field = (self.__handler[args['field'][0]](object, gclass, args['field'][1])
171 | if type(args['field']) is list
172 | else object.get(args['field']))
173 | if v_field:
174 | for obj_name in self.__data[gclass]:
175 | v_key = (args['key'][0](object, gclass, obj_name, args['key'][1])
176 | if type(args['key']) is list
177 | else self.__data[gclass][obj_name].get(args['key']))
178 | if (((type(v_field) is list) and (v_key in v_field))
179 | or ((type(v_key) is list) and (v_field in v_key))
180 | or (v_key == v_field)):
181 | res.append(obj_name)
182 | return res
183 |
184 | @GeneralException
185 | def __package_manager(self, object, gclass, args):
186 | if not args.get('field'): return []
187 | provider = object.get(args['field'])
188 | managers = Config.managers_eq
189 | platform = Config.platform
190 | if provider and platform and managers and (provider in managers):
191 | package = managers[provider]
192 | if package not in self.__data['packages'] and managers['_autoadd']:
193 | self.__add_obj.setdefault(gclass, {})
194 | self.__add_obj[gclass][package] = self.__obj_maker['manager'](package, platform)
195 | return [package]
196 | elif package in self.__data['packages']:
197 | return [package]
198 | return []
199 |
--------------------------------------------------------------------------------
/pysa/exception.py:
--------------------------------------------------------------------------------
1 | '''
2 | Exception handler
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import logging
24 |
25 | class ScannerException(Exception): pass
26 |
27 | # Decorator
28 | def GeneralException(func):
29 | def __action_with_decorator(self, *args, **kwargs):
30 | try:
31 | class_name = self.__class__.__name__
32 | func_name = func.__name__
33 | return func(self, *args, **kwargs)
34 | except Exception, e:
35 | logging.error("%s.%s() error: %s" % (class_name, func_name, str(e)))
36 | raise ScannerException, e
37 | return __action_with_decorator
38 |
--------------------------------------------------------------------------------
/pysa/filter/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/filter/__init__.py
--------------------------------------------------------------------------------
/pysa/filter/filter.py:
--------------------------------------------------------------------------------
1 | '''
2 | Apply user filters
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from pysa.tools import *
24 | from pysa.exception import *
25 |
26 |
27 | # filter actions
28 | class Filter():
29 | def __init__(self, filters):
30 | self.f = filters
31 |
32 | # preprocessing on packages section
33 | @GeneralException
34 | def update_package(self, package, pkg_name, update):
35 | Tools.l(INFO, "selection update packages", 'update_package', self)
36 | if (not self.f) or (not self.f.get('update')):
37 | return package
38 | mode = (self.f['update']['_update'] if self.f['update'].get('_update') else False)
39 | excp = self.f['update'].get('except')
40 | if self.exception_filter(mode, excp, pkg_name, ["*", ".*"]):
41 | package['version'] = update
42 | return package
43 |
44 | # item replacement
45 | @GeneralException
46 | def item_replace(self, gclass, key, val, name, eq = None):
47 | if not self.f:
48 | return val
49 | global1 = self.f.get('replace')
50 | global2 = (global1.get(gclass) if global1 else None)
51 | section = (global2.get(key) if global2 else None)
52 |
53 | replacelist = Tools.dict_merging(Tools.dict_merging(global1, global2), section)
54 | if not replacelist:
55 | return val
56 |
57 | mode = (replacelist.pop('_replaceall') if replacelist.get('_replaceall') != None else True)
58 | excp = (replacelist.pop('_except') if replacelist.get('_except') != None else None)
59 |
60 | replacelist = Tools.dict_cleaner(replacelist)
61 | if not replacelist:
62 | return val
63 |
64 | if (excp == None
65 | or self.exception_filter(mode, excp, name, eq)):
66 | for i in replacelist:
67 | c = val
68 | for data in replacelist[i]:
69 | if (type(val) != list) and (type(val) != dict):
70 | val = re.sub("%s" % (data),
71 | "%s" % (i),
72 | "%s" % (val))
73 | if c != val:
74 | Tools.l(INFO,
75 | "values updated for item %s in section %s"
76 | % (name, key),
77 | 'item_replace',
78 | self)
79 | return val
80 |
81 | # apply filter, replace: ["old", "new"]
82 | @GeneralException
83 | def exception_filter(self, mode, exceptions, value, exprep = None):
84 | if (exceptions == None and mode == True):
85 | return True
86 | elif (exceptions == None and mode == False):
87 | return False
88 | fl = False
89 | for name in exceptions:
90 | name = "%s$" % (name)
91 | if re.match((name.replace(exprep[0], exprep[1]) if exprep else name), value):
92 | fl = True
93 | break
94 | if (((mode == True) and (fl == False))
95 | or ((mode == False) and (fl == True))):
96 | return True
97 | return False
98 |
--------------------------------------------------------------------------------
/pysa/filter/parser.py:
--------------------------------------------------------------------------------
1 | '''
2 | Apply user filters
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from ConfigParser import SafeConfigParser
24 |
25 | from pysa.tools import *
26 | from pysa.exception import *
27 |
28 |
29 | class ParserException(Exception): pass
30 |
31 | filters_split = ['discard', 'addition']
32 | filters_req = {
33 | 'replace': {
34 | '_replaceall': [True, False]
35 | },
36 | 'update': {
37 | '_update': [True, False]
38 | }
39 | }
40 |
41 | global_filters = {
42 | 'discard': {
43 | 'file' : {
44 | 'path' : [
45 | '/etc/fstab',
46 | '/etc/group',
47 | '/etc/gshadow-',
48 | '/etc/hosts',
49 | '/etc/passwd',
50 | '/etc/passwd-',
51 | '/etc/shadow',
52 | '/etc/shadow-',
53 | ]
54 | }
55 | }
56 | }
57 |
58 |
59 | # filters parser
60 | class FParser():
61 | def __init__(self, filename):
62 | self.__filename = Tools.file_exists(filename)
63 |
64 | # action
65 | @GeneralException
66 | def run(self):
67 | if not self.__filename:
68 | return global_filters
69 | return self.__parse_filters()
70 |
71 | # check required fields
72 | @GeneralException
73 | def __parse_req(self, sec, basename):
74 | if basename in filters_req:
75 | for req in filters_req[basename]:
76 | if sec.get(req) == None:
77 | return False
78 | elif filters_req[basename][req] == None:
79 | continue
80 | elif sec[req] not in filters_req[basename][req]:
81 | return False
82 | return True
83 |
84 | # values parsing
85 | @GeneralException
86 | def __parse_value(self, sec, key, value):
87 | if value == "true" or value == "True":
88 | sec[key] = True
89 | elif value == "false" or value == "False":
90 | sec[key] = False
91 | elif re.search(",", value):
92 | sec[key] = re.split("\s*,\s*", value)
93 | else:
94 | sec[key] = [value]
95 | return sec
96 |
97 | # parse sections
98 | @GeneralException
99 | def __parse_loop(self, parser, sec, name, refname=None):
100 | # define referer name
101 | if not refname:
102 | refname = name
103 |
104 | # get subsections
105 | keys = [refname]
106 | if re.search("\.", refname):
107 | keys = re.split("\.", refname)
108 | basename = keys[0]
109 |
110 | # create subsections
111 | curname = refname
112 | cursec = sec
113 | for key in keys:
114 | if cursec.get(key) == None:
115 | cursec[key] = {}
116 | cursec = cursec[key]
117 |
118 | # content parsing
119 | for key, value in parser.items(name):
120 | if key == '_contentrefer':
121 | if value == name:
122 | raise ParserException, ("filter file error on section %s" % refname)
123 | return self.__parse_loop(parser, sec, value, name)
124 | elif re.search("\.", key) and ((name in filters_split) or (basename in filters_split)):
125 | skey = re.split("\.", key)
126 | if cursec.get(skey[0]) == None:
127 | cursec[skey[0]] = {}
128 | cursec[skey[0]] = self.__parse_value(cursec[skey[0]], skey[1], value)
129 | else:
130 | cursec = self.__parse_value(cursec, key, value)
131 | # check required fields
132 | if (self.__parse_req(cursec, basename) == False):
133 | raise ParserException, ("filter file error on section %s" % refname)
134 | return sec
135 |
136 | # parse filters file
137 | @GeneralException
138 | def __parse_filters(self):
139 | parser = SafeConfigParser()
140 | parser.read(self.__filename)
141 | sec = {}
142 | for name in parser.sections():
143 | sec = self.__parse_loop(parser, sec, name)
144 | return Tools.dict_merging(global_filters, sec)
145 |
--------------------------------------------------------------------------------
/pysa/madeira.py:
--------------------------------------------------------------------------------
1 | '''
2 | Export output to Madeira account
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from pysa.exception import *
24 | from pysa.tools import *
25 |
26 | # TODO
27 | # export data to madeira account
28 | class Madeira():
29 | def __init__(self, user, user_id, output, module):
30 | self.__user = user
31 | self.__user_id = user_id
32 | self.__output = output
33 | self.__module = module
34 |
35 | # send data to Madeira account
36 | def send(self):
37 | Tools.l(ERR, "ABORTING: not yet implemented", func.__name__, self)
38 |
--------------------------------------------------------------------------------
/pysa/output.py:
--------------------------------------------------------------------------------
1 | '''
2 | Output container
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from pysa.exception import *
24 | from pysa.config import *
25 |
26 | # output container
27 | class Output():
28 | def __init__(self):
29 | self.main = ''
30 | self.c = {}
31 |
32 | @GeneralException
33 | def add_dict(self, output, default = ''):
34 | self.c[output] = default
35 |
36 | @GeneralException
37 | def add(self, output, content):
38 | if output:
39 | self.c[output] = self.c.setdefault(output, '')
40 | self.c[output] += content
41 | else:
42 | self.main += content
43 |
44 | @GeneralException
45 | def dump(self, manifest_name=None):
46 | return (self.c[manifest_name] if manifest_name else self.main)
47 |
48 | @GeneralException
49 | def list(self):
50 | l = ([''] if self.main else [])
51 | for seq in ORDER_LIST:
52 | if seq in self.c:
53 | l.append(seq)
54 | return l
55 |
56 | @GeneralException
57 | def mod(self, content, output=None):
58 | if output:
59 | self.c[output] = content
60 | else:
61 | self.main = content
62 |
--------------------------------------------------------------------------------
/pysa/preprocessing.py:
--------------------------------------------------------------------------------
1 | '''
2 | Data preprocessing before modules
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from pysa.tools import *
24 | from pysa.exception import *
25 | from pysa.config import FILE_CLASS, ORDER_LIST
26 |
27 | from pysa.dependencies import Dependencies
28 |
29 | import copy
30 |
31 | FILE_IDENT = FILE_CLASS + [
32 | 'sources',
33 | ]
34 |
35 | # preprocesser
36 | class Preprocessing():
37 | def __init__(self, module):
38 | exec "from %s.objects import *"%module
39 | self.__obj_maker = OBJ_MAKER
40 | self.__cmlabel = CMLABEL
41 | self.__data = None
42 | self.__deps = Dependencies(module)
43 |
44 | # action
45 | @GeneralException
46 | def run(self, data):
47 | self.__data = copy.deepcopy(data)
48 | if self.__data:
49 | if self.__obj_maker.get('objkey'):
50 | self.__keys_mod()
51 | self.__prepross_files()
52 | self.__data = self.__deps.run(self.__data)
53 | return self.__data
54 |
55 | # create unique ids for salt
56 | @GeneralException
57 | def __keys_mod(self):
58 | new_data = {}
59 | for c in self.__data:
60 | if c not in ORDER_LIST: continue
61 | new_data[c] = {}
62 | for obj in self.__data[c]:
63 | key = self.__obj_maker['objkey'](c,obj)
64 | new_data[c][key] = self.__data[c][obj]
65 | self.__data = new_data
66 |
67 | # preprocessing on files section
68 | @GeneralException
69 | def __prepross_files(self):
70 | Tools.l(INFO, "preprocessing files", 'prepross_files', self)
71 | dds = self.__files_iter(self.__file_directory, FILE_IDENT)
72 | for file_item in dds:
73 | self.__data['dirs'] = Tools.s_dict_merging(self.__data.get('dirs'), dds[file_item])
74 | self.__files_iter(self.__file_item_removal, ['dirs']+FILE_CLASS)
75 | Tools.l(INFO, "preprocessing files done", 'prepross_files', self)
76 |
77 | # create config files directory
78 | @GeneralException
79 | def __file_directory(self, container, file_item, files, file, files_l):
80 | if container.get(file_item) == None:
81 | container[file_item] = {}
82 | fp = files[file]['path']
83 | drs = Tools.get_recurse_path(os.path.dirname(fp))
84 | for dr in drs:
85 | if (dr == '/') or (("-%s"%dr) in container[file_item]):
86 | continue
87 | container[file_item]["-%s"%dr] = self.__obj_maker['file'](dr)
88 | return container
89 |
90 | # remove items
91 | @GeneralException
92 | def __file_item_removal(self, container, file_item, files, file, files_l):
93 | r_files_l = files_l[:]
94 | r_files_l.reverse()
95 | for consumed in r_files_l:
96 | if consumed == file_item:
97 | break
98 | elif self.__data.get(consumed):
99 | flag = None
100 | for f in self.__data[consumed]:
101 | path = self.__data[consumed][f]['path']
102 | if path == files[file]['path']:
103 | flag = f
104 | break
105 | if flag:
106 | self.__data[consumed].pop(flag)
107 | return container
108 |
109 | # iterate over files
110 | @GeneralException
111 | def __files_iter(self, action, files_l):
112 | container = {}
113 | for file_item in files_l:
114 | files = (dict(self.__data[file_item].items()) if self.__data.get(file_item) else {})
115 | for file in files:
116 | container = action(container, file_item, files, file, files_l)
117 | return container
118 |
--------------------------------------------------------------------------------
/pysa/puppet/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/puppet/__init__.py
--------------------------------------------------------------------------------
/pysa/puppet/build.py:
--------------------------------------------------------------------------------
1 | '''
2 | Generate puppet scripts
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import re
24 |
25 | from pysa.exception import *
26 | from pysa.config import *
27 | from pysa.tools import *
28 | from pysa.output import Output
29 |
30 | from pysa.puppet.converter import GLOBAL_SEC_EQ
31 |
32 | # define quoted variables
33 | QUOTED_AVOIDED_KEYS = ['content', 'before']
34 | QUOTED_FORCED_KEYS = ['checksum', 'name', 'group', 'owner']
35 | QUOTED_FORCED_CONTENT = ['\W', '\d']
36 |
37 | # puppet generation class
38 | class PuppetBuild():
39 | def __init__(self, input_dict, output_path, module_name):
40 | self.__quoted_regex = "%s" % ('|'.join(QUOTED_FORCED_CONTENT))
41 | self.__module_name = module_name
42 | self.__input_dict = input_dict
43 | self.__output_container = Output()
44 | self.__output_path = output_path+'/'+self.__module_name
45 | self.__curent_manifest = ''
46 |
47 | # main function
48 | @GeneralException
49 | def run(self):
50 | Tools.l(INFO, "running generation engine", 'run', self)
51 | self.__generate(self.__input_dict)
52 | self.__create_init_file()
53 | self.dump_in_files()
54 | Tools.l(INFO, "generation complete", 'run', self)
55 | return True
56 |
57 | # print the puppet files
58 | @GeneralException
59 | def dump(self):
60 | for manifest_name in self.__output_container.list():
61 | manifest_fname = (manifest_name if manifest_name else 'init')
62 | print "%s:\n%s\n\n" % (manifest_fname,
63 | self.__output_container.dump(manifest_name))
64 |
65 | # dump puppet file in variable
66 | @GeneralException
67 | def dump_in_var(self, data=''):
68 | for manifest_name in self.__output_container.list():
69 | manifest_fname = (manifest_name if manifest_name else 'init')
70 | data += ("%s:\n%s\n\n" % (manifest_fname,
71 | self.__output_container.dump(manifest_name)))
72 | return data
73 |
74 | # dump the puppet files into the right files
75 | @GeneralException
76 | def dump_in_files(self):
77 | for manifest_name in self.__output_container.list():
78 | manifest_fname = (manifest_name if manifest_name else 'init')
79 | Tools.write_in_file(self.__output_path+'/manifests/'+manifest_fname+'.pp',
80 | self.__output_container.dump(manifest_name))
81 |
82 | # init file generation
83 | @GeneralException
84 | def __create_init_file(self):
85 | includes = ''
86 | for manifest_name in self.__output_container.list():
87 | if not manifest_name: continue
88 | includes += "include %s\n" % (manifest_name)
89 | content = ''
90 | for line in re.split('\n',
91 | self.__output_container.dump()+'\n'+includes):
92 | if not line: continue
93 | content += re.sub(r'^', r'\n\t', line)
94 | self.__output_container.mod("class %s {\n%s\n}\n" % (self.__module_name, content))
95 |
96 | # particular case for the single instructions
97 | @GeneralException
98 | def __single_instruction(self, parent, sections, section_name, tab):
99 | if not parent:
100 | return tab
101 | for content in sections[section_name]:
102 | if section_name == GLOBAL_SEC_EQ['require']:
103 | if content not in parent: continue
104 | self.__output_container.add(self.__curent_manifest,
105 | "%s%s %s\n" % (tab,section_name[len(SINGLE_SEC):],content))
106 | return tab
107 |
108 | # quote required values
109 | @GeneralException
110 | def __add_quotes(self, key, val):
111 | return (("'%s'" % (re.sub('\'', '\\\'', val))
112 | if (key not in QUOTED_AVOIDED_KEYS)
113 | and ((key in QUOTED_FORCED_KEYS)
114 | or (re.search(self.__quoted_regex, val)))
115 | else val)
116 | if type(val) is str else val)
117 |
118 | # content writing
119 | @GeneralException
120 | def __write_content(self, section_name, label, optlabel, content):
121 | out = ''
122 | out_size = 0
123 | if (type(content) is list):
124 | for value in content:
125 | out += (", " if out_size else '')+"'%s'" % (value)
126 | out_size += 1
127 | if out_size:
128 | return "%s%s%s" % (("[" if out_size > 1 else ''),out,("]" if out_size > 1 else ''))
129 | elif (type(content) is dict):
130 | for value_type in content:
131 | for value in content[value_type]:
132 | out += (", " if out_size else '') + "%s['%s']" % (value_type,value)
133 | out_size += 1
134 | if out_size:
135 | return "%s%s%s" % (("[" if out_size > 1 else ''),out,("]" if out_size > 1 else ''))
136 | else:
137 | if (self.__curent_manifest in FILE_CLASS) and (label[0] != '-') and optlabel == 'content':
138 | filename = ('/' if label[0] != '/' else '')+label
139 | Tools.write_in_file(self.__output_path+'/templates'+filename, content)
140 | content = "template('%s')" % (self.__module_name+filename)
141 | return self.__add_quotes(optlabel, content)
142 | return None
143 |
144 | # global content generation for pupept config file
145 | @GeneralException
146 | def __create_content(self, parent, data, section_name, tab):
147 | Tools.l(INFO, "creating section %s" % (section_name.lstrip(VOID_EQ)), 'create_content', self)
148 | if section_name[:len(SINGLE_SEC)] == SINGLE_SEC:
149 | return self.__single_instruction(parent, data, section_name, tab)
150 | self.__output_container.add(self.__curent_manifest, "%s%s {\n" % (tab,section_name.lstrip(ACTION_ID)))
151 | for label in sorted(data[section_name]):
152 | if label in NULL:
153 | continue
154 | if label[0] != ACTION_ID:
155 | tab = Tools.tab_inc(tab)
156 | self.__output_container.add(self.__curent_manifest, "%s'%s':\n" % (tab,label))
157 | tab = Tools.tab_inc(tab)
158 | wrote = False
159 | for optlabel in sorted(data[section_name][label]):
160 | if (data[section_name][label][optlabel] not in NULL) and (optlabel[0] != ACTION_ID):
161 | out = self.__write_content(section_name,
162 | label,
163 | optlabel,
164 | data[section_name][label][optlabel])
165 | if out:
166 | self.__output_container.add(self.__curent_manifest,
167 | "%s%s%s => %s"%((",\n" if wrote else ''),tab,optlabel,out))
168 | wrote = True
169 | if wrote and label != MAIN_SECTION:
170 | self.__output_container.add(self.__curent_manifest, ";\n")
171 | elif wrote and label == MAIN_SECTION:
172 | self.__output_container.add(self.__curent_manifest, "\n")
173 | tab = Tools.tab_dec(tab)
174 | if label[0] != ACTION_ID:
175 | tab = Tools.tab_dec(tab)
176 | self.__output_container.add(self.__curent_manifest, "%s}\n" % (tab))
177 | return tab
178 |
179 | # class generation method, applies the recursion
180 | @GeneralException
181 | def __create_class(self, parent, data, section_name, tab):
182 | Tools.l(INFO, "generation class %s" % (section_name), 'create_class', self)
183 | self.__output_container.add(self.__curent_manifest, "%sclass %s {\n" % (tab,section_name))
184 | tab = Tools.tab_inc(tab)
185 | # recursion here
186 | tab = self.__generate(data[section_name], data, tab)
187 | tab = Tools.tab_dec(tab)
188 | self.__output_container.add(self.__curent_manifest, "%s}\n%sinclude %s\n" % (tab,tab,section_name))
189 | return tab
190 |
191 | # puppet file generation function
192 | # recursive function
193 | @GeneralException
194 | def __generate(self, data, parent = None, tab=''):
195 | # adding Exec section
196 | if GLOBAL_SEC_EQ['Exec'] in data:
197 | tab = self.__create_content(parent, data, GLOBAL_SEC_EQ['Exec'], tab)
198 | # global generation
199 | for section_name in sorted(data):
200 | # avoid exception
201 | if section_name == GLOBAL_SEC_EQ['Exec']:
202 | continue
203 | # content found
204 | elif section_name[0] == ACTION_ID and self.__curent_manifest:
205 | tab = self.__create_content(parent, data, section_name, tab)
206 | continue
207 | # new class
208 | if not parent:
209 | self.__curent_manifest = section_name
210 | self.__output_container.add_dict(self.__curent_manifest)
211 | # recursion here
212 | tab = self.__create_class(parent, data, section_name, tab)
213 | return tab
214 |
--------------------------------------------------------------------------------
/pysa/puppet/converter.py:
--------------------------------------------------------------------------------
1 | '''
2 | Dictionnary converter for puppet scripts generation
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import re
24 | import copy
25 |
26 | from pysa.tools import *
27 | from pysa.config import *
28 | from pysa.exception import *
29 |
30 | from pysa.filter.filter import Filter
31 |
32 |
33 | # define _order section
34 |
35 | # list of ordered sections
36 | ORDERED_LIST_EQ = ['sources']
37 |
38 | # general modifiers
39 | GLOBAL_SEC_EQ = {
40 | 'Exec' : ACTION_ID+'Exec',
41 | 'exec' : ACTION_ID+'exec',
42 | 'order' : ACTION_ID+'order',
43 | 'require' : SINGLE_SEC+'require'
44 | }
45 |
46 | # define _Exec section
47 | GLOBAL_EXEC_EQ = {
48 | 'path' : '/usr/bin:/bin:/usr/sbin:/sbin'
49 | }
50 |
51 | # define _exec section
52 | EXEC_EQ = {
53 | 'apt' : 'apt-get update',
54 | 'yum' : '/usr/sbin/yum-complete-transaction',
55 | 'pip' : 'easy_install pip',
56 | }
57 |
58 | # define general sections
59 | SECTION_EQ = {
60 | 'dirs' : ACTION_ID+'file',
61 | 'files' : ACTION_ID+'file',
62 | 'packages' : ACTION_ID+'package',
63 | 'services' : ACTION_ID+'service',
64 | 'crons' : ACTION_ID+'cron',
65 | 'groups' : ACTION_ID+'group',
66 | 'mounts' : ACTION_ID+'mount',
67 | 'hosts' : ACTION_ID+'host',
68 | 'repos' : ACTION_ID+'file',
69 | 'keys' : ACTION_ID+'file',
70 | 'users' : ACTION_ID+'user',
71 | 'sources' : ACTION_ID+'vcsrepo'
72 | }
73 | SECTION_CALL_EQ = dict([(key,SECTION_EQ[key].capitalize()) for key in SECTION_EQ])
74 |
75 | # define subsclasses equivalency
76 | SUBCLASS_EQ = {
77 | 'packages' : {
78 | MAIN_SECTION : 'provider',
79 | 'order' : [
80 | ['apt', 'yum', 'rpm'],
81 | ['npm', 'pecl', 'pear', 'pip', 'gem']
82 | ]
83 | }
84 | }
85 |
86 | # add 'require' instruction
87 | REQUIRE_EQ = [
88 | SUBCLASS_EQ['packages']['order']
89 | ]
90 |
91 | # key modifier
92 | CONTENTKEY_EQ = {
93 | MAIN_SECTION : {
94 | 'version' : 'ensure',
95 | 'key' : 'content'
96 | },
97 | 'sources' : {
98 | 'scm' : 'provider'
99 | },
100 | 'users' : {
101 | 'group' : 'gid'
102 | }
103 | }
104 |
105 | # val modifier (on key)
106 | CONTENTVAL_EQ = {
107 | 'packages' : {
108 | 'provider' : ['php', 'pear']
109 | }
110 | }
111 |
112 | # content add
113 | CONTENTADD_EQ = {
114 | 'sources' : {
115 | 'ensure' : 'present'
116 | },
117 | 'groups' : {
118 | 'ensure' : 'present'
119 | }
120 | }
121 |
122 | # avoided sections
123 | AVOIDSEC_EQ = {
124 | 'mounts' : ['size'],
125 | 'packages' : ['manager', 'config_files'],
126 | 'sources' : ['mode', 'password', 'branch', 'name', 'key'],
127 | 'groups' : ['gid'],
128 | 'users' : ['uid', 'gid'],
129 | 'repos' : ['provider'],
130 | }
131 |
132 | # Append sections
133 | APPSEC_EQ = {
134 | 'crons' : ['environment', 'PATH=']
135 | }
136 |
137 | class PuppetConverter():
138 | def __init__(self, minput, filters = None):
139 | self.__output = {}
140 | self.__input = copy.deepcopy(minput)
141 | self.__filter = Filter(filters)
142 | self.__prev_obj = None
143 |
144 | # main method
145 | @GeneralException
146 | def run(self):
147 | Tools.l(INFO, "running", 'run', self)
148 |
149 | #empty imput
150 | if not self.__input:
151 | Tools.l(ERR, "empty input", 'run', self)
152 | return {}
153 |
154 | # convert
155 | self.__generate_classes(self.__input)
156 |
157 | # add exceptions
158 | if GLOBAL_EXEC_EQ:
159 | self.__add_global_exec()
160 |
161 | Tools.l(INFO, "complete", 'run', self)
162 | return self.__output
163 |
164 | # generate global exec
165 | @GeneralException
166 | def __add_global_exec(self):
167 | Tools.l(INFO, "adding Exec section", 'add_global_exec', self)
168 | self.__output[GLOBAL_SEC_EQ['Exec']] = {MAIN_SECTION : {}}
169 | for key in GLOBAL_EXEC_EQ:
170 | Tools.l(INFO, "adding key %s" % (key), 'add_global_exec', self)
171 | self.__output[GLOBAL_SEC_EQ['Exec']][MAIN_SECTION][key] = self.__process_values('', 'Exec', key, GLOBAL_EXEC_EQ[key])
172 |
173 | # generate sub execs
174 | @GeneralException
175 | def __add_top_class(self, key):
176 | c = {}
177 | for order in REQUIRE_EQ:
178 | if key in order[0]: break
179 | elif key in order[1]:
180 | req = []
181 | for r in order[0]:
182 | req.append(r)
183 | if req:
184 | c = Tools.dict_merging(c, {
185 | GLOBAL_SEC_EQ['require'] : req
186 | })
187 | if key in EXEC_EQ:
188 | Tools.l(INFO, "adding exec section for %s" % (key), 'add_exec', self)
189 | c = Tools.dict_merging(c, {
190 | GLOBAL_SEC_EQ['exec'] : {
191 | EXEC_EQ[key] : GLOBAL_EXEC_EQ
192 | }
193 | })
194 | return c
195 |
196 | # processing on values
197 | @GeneralException
198 | def __process_values(self, gclass, name, key, val):
199 | if type(val) is int:
200 | val = "%s" % (val)
201 | elif (type(val) is not str) or (not val):
202 | return val
203 | if (gclass in APPSEC_EQ) and (key == APPSEC_EQ[gclass][0]):
204 | val = APPSEC_EQ[gclass][1] + val
205 | return self.__filter.item_replace(gclass, key, val, name)
206 |
207 |
208 | # processing on data
209 | @GeneralException
210 | def __process_data(self, input, gclass, name, cur_class):
211 | Tools.l(INFO, "processing data", 'process_data', self)
212 | # modifications
213 | kcontent = Tools.list_merging(AVOIDSEC_EQ.get(MAIN_SECTION), AVOIDSEC_EQ.get(gclass))
214 | for key in kcontent:
215 | if key in input:
216 | input[key] = None
217 | kcontent = Tools.s_dict_merging(CONTENTADD_EQ.get(MAIN_SECTION), CONTENTADD_EQ.get(gclass), False)
218 | for key in kcontent:
219 | input[key] = kcontent[key]
220 | kcontent = Tools.s_dict_merging(CONTENTKEY_EQ.get(MAIN_SECTION), CONTENTKEY_EQ.get(gclass), False)
221 | for key in kcontent:
222 | if key in input:
223 | input[kcontent[key]] = input.pop(key)
224 | kcontent = Tools.s_dict_merging(CONTENTVAL_EQ.get(MAIN_SECTION), CONTENTVAL_EQ.get(gclass), False)
225 | for key in kcontent:
226 | if key in input:
227 | if input[key] == kcontent[key][0]:
228 | input[key] = kcontent[key][1]
229 |
230 | # exec dependency
231 | if cur_class in EXEC_EQ:
232 | input['require'] = Tools.dict_merging(input.get('require'), {
233 | GLOBAL_SEC_EQ['exec'][len(ACTION_ID):].capitalize() : [
234 | EXEC_EQ[cur_class]
235 | ]
236 | })
237 |
238 | # main loop
239 | for key in input:
240 | if type(input[key]) is list:
241 | store = []
242 | for d in input[key]:
243 | store.append(self.__process_values(gclass, name, key, d))
244 | input[key] = store
245 | else:
246 | input[key] = self.__process_values(gclass, name, key, input[key])
247 | return input
248 |
249 | # processing on section
250 | @GeneralException
251 | def __process_sec(self, data, gclass, name, cur_class):
252 | Tools.l(INFO, "creating section %s" % (SECTION_EQ[gclass]), 'process_sec', self)
253 | if (SECTION_EQ[gclass] == ACTION_ID+'package'):
254 | data[gclass][name] = self.__filter.update_package(data[gclass][name], name, 'latest')
255 | return self.__process_data(data[gclass][name], gclass, name, cur_class)
256 |
257 | # class generation
258 | @GeneralException
259 | def __generate_classes(self, data):
260 | for gclass in data:
261 | if gclass not in SECTION_EQ:
262 | Tools.l(INFO, "Ignored unknown class %s" % (gclass), 'generate_classes', self)
263 | continue
264 | Tools.l(INFO, "creating class %s" % (gclass), 'generate_classes', self)
265 | self.__prev_obj = None
266 | self.__output[gclass] = self.__add_top_class(gclass)
267 | for name in sorted(data[gclass]):
268 | if gclass in SUBCLASS_EQ:
269 | subkey = data[gclass][name][SUBCLASS_EQ[gclass][MAIN_SECTION]]
270 | Tools.l(INFO, "creating sub class %s" % (subkey), 'generate_classes', self)
271 | self.__output[gclass].setdefault(subkey, self.__add_top_class(subkey))
272 | self.__output[gclass][subkey].setdefault(SECTION_EQ[gclass], {})
273 | self.__output[gclass][subkey][SECTION_EQ[gclass]][name] = self.__process_sec(data, gclass, name, subkey)
274 | else:
275 | self.__output[gclass].setdefault(SECTION_EQ[gclass], {})
276 | self.__output[gclass][SECTION_EQ[gclass]][name] = self.__process_sec(data, gclass, name, gclass)
277 | self.__prev_obj = name
278 |
--------------------------------------------------------------------------------
/pysa/puppet/objects.py:
--------------------------------------------------------------------------------
1 | '''
2 | Puppet Objects
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | from pysa.exception import *
24 |
25 | from pysa.scanner.actions.utils import get_stat
26 | from pysa.dependencies import *
27 |
28 | CMLABEL="puppet"
29 |
30 | class PuppetObjects():
31 | @staticmethod
32 | def puppet_file_dir_obj(dr):
33 | # get group, mode and owner
34 | s = get_stat(dr)
35 | #DEBUG
36 | #s = ('root', oct(0777), 'root')
37 | #/DEBUG
38 | return {
39 | 'path' : dr,
40 | 'ensure' : 'directory',
41 | 'name' : dr,
42 | 'group' : s[0],
43 | 'mode' : s[1],
44 | 'owner' : s[2],
45 | }
46 |
47 | @staticmethod
48 | def puppet_pkg_manager_obj(package, provider):
49 | return {
50 | 'name' : package,
51 | 'provider' : provider,
52 | 'version' : 'latest',
53 | }
54 |
55 |
56 | OBJ_MAKER = {
57 | 'file' : PuppetObjects.puppet_file_dir_obj,
58 | 'manager' : PuppetObjects.puppet_pkg_manager_obj,
59 | }
60 |
61 | DEPENDENCIES = {
62 | 'hosts' : {},
63 | 'mounts' : {
64 | 'hosts' : 'Class',
65 | 'mounts' : [
66 | BASED_ON_FIELD, {
67 | 'field' : [SELF_ORDER, {
68 | 'field' : 'name',
69 | }],
70 | 'key' : 'name',
71 | }
72 | ],
73 | },
74 | 'groups' : {
75 | 'hosts' : 'Class',
76 | },
77 | 'users' : {
78 | 'hosts' : 'Class',
79 | 'groups' : [
80 | BASED_ON_FIELD, {
81 | 'field' : 'group',
82 | 'key' : 'name',
83 | }
84 | ],
85 | },
86 | 'dirs' : {
87 | 'hosts' : 'Class',
88 | 'dirs' : [
89 | BASED_ON_FIELD, {
90 | 'field' : [SELF_ORDER, {
91 | 'field' : 'path',
92 | }],
93 | 'key' : 'path',
94 | }
95 | ],
96 | 'groups' : [
97 | BASED_ON_FIELD, {
98 | 'field' : 'group',
99 | 'key' : 'name',
100 | }
101 | ],
102 | 'users' : [
103 | BASED_ON_FIELD, {
104 | 'field' : 'owner',
105 | 'key' : 'name',
106 | }
107 | ],
108 | 'mounts' : [
109 | BASED_ON_FIELD, {
110 | 'field' : [GET_MOUNT_FROM_PATH, {
111 | 'field' : 'path',
112 | }],
113 | 'key' : 'device',
114 | }
115 | ],
116 | },
117 | 'keys' : {
118 | 'hosts' : 'Class',
119 | 'groups' : [
120 | BASED_ON_FIELD, {
121 | 'field' : 'group',
122 | 'key' : 'name',
123 | }
124 | ],
125 | 'users' : [
126 | BASED_ON_FIELD, {
127 | 'field' : 'owner',
128 | 'key' : 'name',
129 | }
130 | ],
131 | 'dirs' : [
132 | BASED_ON_FIELD, {
133 | 'field' : [GET_BASE_PATH, {
134 | 'field' : 'path',
135 | }],
136 | 'key' : 'path',
137 | }
138 | ],
139 | },
140 | 'repos' : {
141 | 'hosts' : 'Class',
142 | 'groups' : [
143 | BASED_ON_FIELD, {
144 | 'field' : 'group',
145 | 'key' : 'name',
146 | }
147 | ],
148 | 'users' : [
149 | BASED_ON_FIELD, {
150 | 'field' : 'owner',
151 | 'key' : 'name',
152 | }
153 | ],
154 | 'dirs' : [
155 | BASED_ON_FIELD, {
156 | 'field' : [GET_BASE_PATH, {
157 | 'field' : 'path',
158 | }],
159 | 'key' : 'path',
160 | }
161 | ],
162 | },
163 | 'packages' : {
164 | 'hosts' : 'Class',
165 | 'repos' : [
166 | BASED_ON_FIELD, {
167 | 'field' : 'provider',
168 | 'key' : 'provider',
169 | }
170 | ],
171 | 'dirs' : [
172 | BASED_ON_FIELD, {
173 | 'field' : [GET_BASE_PATH, {
174 | 'field' : 'path',
175 | }],
176 | 'key' : 'path',
177 | }
178 | ],
179 | },
180 | 'files' : {
181 | 'hosts' : 'Class',
182 | 'groups' : [
183 | BASED_ON_FIELD, {
184 | 'field' : 'group',
185 | 'key' : 'name',
186 | }
187 | ],
188 | 'users' : [
189 | BASED_ON_FIELD, {
190 | 'field' : 'owner',
191 | 'key' : 'name',
192 | }
193 | ],
194 | 'dirs' : [
195 | BASED_ON_FIELD, {
196 | 'field' : [GET_BASE_PATH, {
197 | 'field' : 'path',
198 | }],
199 | 'key' : 'path',
200 | }
201 | ],
202 | 'packages' : [
203 | BASED_ON_FIELD, {
204 | 'field' : 'path',
205 | 'key' : 'config_files',
206 | }
207 | ],
208 | },
209 | 'crons' : {
210 | 'hosts' : 'Class',
211 | 'users' : [
212 | BASED_ON_FIELD, {
213 | 'field' : 'user',
214 | 'key' : 'name',
215 | }
216 | ],
217 | },
218 | 'sources' : {
219 | 'hosts' : 'Class',
220 | 'sources' : [
221 | BASED_ON_FIELD, {
222 | 'field' : [SELF_ORDER, {
223 | 'field' : 'path',
224 | }],
225 | 'key' : 'path',
226 | }
227 | ],
228 | 'groups' : [
229 | BASED_ON_FIELD, {
230 | 'field' : 'group',
231 | 'key' : 'name',
232 | }
233 | ],
234 | 'users' : [
235 | BASED_ON_FIELD, {
236 | 'field' : 'owner',
237 | 'key' : 'name',
238 | }
239 | ],
240 | 'dirs' : [
241 | BASED_ON_FIELD, {
242 | 'field' : [GET_BASE_PATH, {
243 | 'field' : 'path',
244 | }],
245 | 'key' : 'path',
246 | }
247 | ],
248 | 'keys' : [
249 | BASED_ON_FIELD, {
250 | 'field' : 'key',
251 | 'key' : 'name',
252 | }
253 | ],
254 | 'packages' : [
255 | BASED_ON_FIELD, {
256 | 'field' : [GET_PKG_FROM_SCM, {
257 | 'field' : 'scm',
258 | }],
259 | 'key' : 'name',
260 | }
261 | ],
262 | },
263 | 'services' : {
264 | 'files' : 'Class',
265 | },
266 | }
267 |
--------------------------------------------------------------------------------
/pysa/salt/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/salt/__init__.py
--------------------------------------------------------------------------------
/pysa/salt/build.py:
--------------------------------------------------------------------------------
1 | '''
2 | Generate salt manifests
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import re
24 |
25 | from pysa.exception import *
26 | from pysa.config import *
27 | from pysa.tools import *
28 | from pysa.output import Output
29 |
30 | # salt generation class
31 | class SaltBuild():
32 | def __init__(self, input_dict, output_path, module_name):
33 | self.__module_name = module_name
34 | self.__input_dict = input_dict
35 | self.__output_container = Output()
36 | self.__output_path = output_path+'/'+self.__module_name
37 | self.__curent_manifest = None
38 | self.__curent_state = None
39 | self.__curent_name = None
40 |
41 | # main function
42 | @GeneralException
43 | def run(self):
44 | Tools.l(INFO, "running generation engine", 'run', self)
45 | self.__generate(self.__input_dict)
46 | self.__create_init_file()
47 | self.dump_in_files()
48 | Tools.l(INFO, "generation complete", 'run', self)
49 | return True
50 |
51 | # print the puppet files
52 | @GeneralException
53 | def dump(self):
54 | for manifest_name in self.__output_container.list():
55 | manifest_fname = (manifest_name if manifest_name else 'init')
56 | print "%s:\n%s\n\n" % (manifest_fname,
57 | self.__output_container.dump(manifest_name))
58 |
59 | # dump puppet file in variable
60 | @GeneralException
61 | def dump_in_var(self, data=''):
62 | for manifest_name in self.__output_container.list():
63 | manifest_fname = (manifest_name if manifest_name else 'init')
64 | data += ("%s:\n%s\n\n" % (manifest_fname,
65 | self.__output_container.dump(manifest_name)))
66 | return data
67 |
68 | # dump the puppet files into the right files
69 | @GeneralException
70 | def dump_in_files(self):
71 | for manifest_name in self.__output_container.list():
72 | manifest_fname = (manifest_name if manifest_name else 'init')
73 | Tools.write_in_file(self.__output_path+'/'+manifest_fname+'.sls',
74 | self.__output_container.dump(manifest_name))
75 |
76 | # init file generation
77 | @GeneralException
78 | def __create_init_file(self):
79 | self.__output_container.add(None, "include:\n")
80 | for manifest in self.__output_container.list():
81 | if not manifest: continue
82 | self.__output_container.add(None, " - %s.%s\n"%(self.__module_name,manifest))
83 |
84 | # content writing
85 | @GeneralException
86 | def __write_content(self, key, val, tab):
87 | if (self.__curent_manifest in FILE_CLASS) and key == 'source':
88 | name = self.__input_dict[self.__curent_manifest][self.__curent_state][self.__curent_name]['name']
89 | filename = "%s" % (('/' if name[0] != '/' else '')+name)
90 | Tools.write_in_file(self.__output_path+'/templates'+filename, val)
91 | val = "salt://%s" % (self.__module_name+'/templates'+filename)
92 | self.__output_container.add(self.__curent_manifest, "%s"%(val))
93 |
94 | # section generation (recursive)
95 | @GeneralException
96 | def __create_section(self, key, val, tab):
97 | if (key in NULL) or (val == None): return
98 | self.__output_container.add(self.__curent_manifest, "%s- %s"%(tab,key))
99 | if val == MAIN_SECTION:
100 | self.__output_container.add(self.__curent_manifest, "\n")
101 | elif type(val) is dict:
102 | tab += " "
103 | self.__output_container.add(self.__curent_manifest, ":\n")
104 | for sub_key in val:
105 | if key == "require":
106 | for itm in val[sub_key]:
107 | self.__create_section("%s: %s"%(sub_key,itm), MAIN_SECTION, tab)
108 | else:
109 | self.__create_section(sub_key, val[sub_key], tab)
110 | elif type(val) is list:
111 | tab += " "
112 | self.__output_container.add(self.__curent_manifest, ":\n")
113 | for d in val:
114 | self.__create_section(d, MAIN_SECTION, tab)
115 | else:
116 | self.__output_container.add(self.__curent_manifest, ": ")
117 | self.__write_content(key, val, tab)
118 | self.__output_container.add(self.__curent_manifest, "\n")
119 |
120 | # global content generation for salt config file
121 | @GeneralException
122 | def __create_content(self, data, manifest, state, name):
123 | cur_data = data[manifest][state][name]
124 | self.__output_container.add(self.__curent_manifest, "%s:\n"%(name if name[0] != '-' else name[1:]))
125 | self.__output_container.add(self.__curent_manifest, " %s:\n"%(state[len(ACTION_ID):]))
126 | for key in cur_data:
127 | self.__create_section(key, cur_data[key], " ")
128 |
129 | # puppet file generation function
130 | @GeneralException
131 | def __generate(self, data):
132 | # global generation
133 | for manifest in sorted(data):
134 | Tools.l(INFO, "generation manifest %s" % (manifest), 'generate', self)
135 | self.__curent_manifest = manifest
136 | for state in sorted(data[manifest]):
137 | self.__curent_state = state
138 | Tools.l(INFO, "module state %s" % (state), 'generate', self)
139 | for name in sorted(data[manifest][state]):
140 | self.__curent_name = name
141 | Tools.l(INFO, "item %s" % (name), 'generate', self)
142 | self.__create_content(data, manifest, state, name)
143 |
--------------------------------------------------------------------------------
/pysa/salt/converter.py:
--------------------------------------------------------------------------------
1 | '''
2 | Dictionnary converter for salt scripts generation
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import re
24 | import hashlib
25 | import copy
26 |
27 | from pysa.tools import *
28 | from pysa.config import *
29 | from pysa.exception import *
30 |
31 | from pysa.filter.filter import Filter
32 |
33 |
34 | def handler_files_checksum(old, content):
35 | contents = ['content','key','source']
36 | c = None
37 | for c_name in contents:
38 | c = content.get(c_name)
39 | if c: break
40 | if not c: return None
41 | return "md5=%s"%(hashlib.md5(c).hexdigest())
42 |
43 | def handler_actionkey_pkg(content):
44 | if (content.get('version') == 'latest') or not content.get('version'):
45 | content['version'] = None
46 | return 'latest'
47 | return 'installed'
48 |
49 | def handler_hosts_names(old, content):
50 | return (old if type(old) is list else [old])
51 |
52 | # TODO: sources
53 |
54 | # define general sections
55 | SECTION_EQ = {
56 | # 'dirs' : ACTION_ID+'file',
57 | 'files' : ACTION_ID+'file',
58 | 'packages' : {
59 | 'key' : 'provider',
60 | 'apt' : ACTION_ID+'pkg',
61 | 'yum' : ACTION_ID+'pkg',
62 | 'rpm' : ACTION_ID+'pkg',
63 | # 'php' : ACTION_ID+'pecl',
64 | 'pecl' : ACTION_ID+'pecl',
65 | 'pear' : ACTION_ID+'pecl',
66 | 'pip' : ACTION_ID+'pip',
67 | 'npm' : ACTION_ID+'npm',
68 | 'gem' : ACTION_ID+'gem',
69 | },
70 | 'services' : ACTION_ID+'service',
71 | 'crons' : ACTION_ID+'cron',
72 | 'groups' : ACTION_ID+'group',
73 | 'mounts' : ACTION_ID+'mount',
74 | 'hosts' : ACTION_ID+'host',
75 | 'repos' : ACTION_ID+'file',
76 | 'keys' : ACTION_ID+'file',
77 | 'users' : ACTION_ID+'user',
78 | # 'sources' : {
79 | # 'key' : 'provider',
80 | # 'git' : ACTION_ID+'git',
81 | # 'svn' : ACTION_ID+'svn',
82 | # 'hg' : ACTION_ID+'hg',
83 | # }
84 | }
85 | SECTION_CALL_EQ = SECTION_EQ
86 |
87 | # avoided sections
88 | AVOIDSEC_EQ = {
89 | 'cron' : ['environment', 'name', 'target'],
90 | 'files' : ['provider','recurse','recurselimit','source'],
91 | 'groups' : ['member','gid'],
92 | 'hosts' : ['target'],
93 | 'mounts' : ['atboot','size'],
94 | ACTION_ID+'pkg' : ['config_files','description','responsefile','provider','instance','category','platform','root','manager','vendor'],
95 | ACTION_ID+'pecl' : ['version','config_files','description','responsefile','provider','instance','category','platform','root','manager','vendor'],
96 | ACTION_ID+'gem' : ['version','config_files','description','responsefile','provider','instance','category','platform','root','manager','vendor'],
97 | ACTION_ID+'npm' : ['version','config_files','description','responsefile','provider','instance','category','platform','root','manager','vendor'],
98 | ACTION_ID+'pip' : ['version','config_files','description','responsefile','provider','instance','category','platform','root','manager','vendor'],
99 | 'repos' : ['provider','recurse','recurselimit','source'],
100 | 'services' : ['hasrestart','path','provider','binary','control','ensure','hasstatus','manifest','start','stop','restart'],
101 | 'keys' : ['target','host_aliases','type','name'],
102 | 'users' : ['uid', 'gid', 'expiry'],
103 | }
104 | # content add
105 | CONTENTADD_EQ = {
106 | 'files' : {
107 | 'makedirs' : 'True',
108 | },
109 | 'mounts' : {
110 | 'mkmnt' : 'True',
111 | },
112 | 'repos' : {
113 | 'makedirs' : 'True',
114 | },
115 | 'keys' : {
116 | 'source_hash' : 'md5',
117 | },
118 | 'services' : {
119 | 'enable' : 'True',
120 | },
121 | }
122 | # key modifier
123 | CONTENTKEY_EQ = {
124 | MAIN_SECTION : {
125 | },
126 | 'crons' : {
127 | 'command' : 'name',
128 | 'monthday' : 'daymonth',
129 | 'weekday' : 'dayweek',
130 | },
131 | 'files' : {
132 | 'checksum' : 'source_hash',
133 | 'content' : 'source',
134 | 'owner' : 'user',
135 | 'path' : 'name',
136 | 'force' : 'replace',
137 | },
138 | 'hosts' : {
139 | 'name' : 'names',
140 | 'host_aliases' : 'names',
141 | },
142 | 'mount' : {
143 | 'remounts' : 'remount',
144 | 'options' : 'opts',
145 | },
146 | 'repos' : {
147 | 'checksum' : 'source_hash',
148 | 'content' : 'source',
149 | 'owner' : 'user',
150 | 'path' : 'name',
151 | 'force' : 'replace',
152 | },
153 | 'keys' : {
154 | 'key' : 'source',
155 | 'content' : 'source',
156 | 'path' : 'name',
157 | },
158 | 'users' : {
159 | 'group' : 'gid',
160 | }
161 | }
162 | # val modifier (on key)
163 | CONTENTVAL_EQ = {
164 | 'files' : {
165 | 'source_hash' : [MAIN_SECTION,handler_files_checksum],
166 | },
167 | 'repos' : {
168 | 'source_hash' : [MAIN_SECTION,handler_files_checksum],
169 | },
170 | 'keys' : {
171 | 'source_hash' : [MAIN_SECTION,handler_files_checksum],
172 | },
173 | 'hosts' : {
174 | 'names' : [MAIN_SECTION,handler_hosts_names],
175 | },
176 | }
177 |
178 | # action key
179 | ACTIONKEY_EQ = {
180 | 'crons' : 'present',
181 | 'files' : 'managed',
182 | 'groups' : 'present',
183 | 'hosts' : 'present',
184 | 'mounts' : 'mounted',
185 | ACTION_ID+'pkg' : handler_actionkey_pkg,
186 | ACTION_ID+'pecl' : 'installed',
187 | ACTION_ID+'gem' : 'installed',
188 | ACTION_ID+'npm' : 'installed',
189 | ACTION_ID+'pip' : 'installed',
190 | 'repos' : 'managed',
191 | 'services' : 'running',
192 | 'keys' : 'managed',
193 | 'users' : 'present',
194 | }
195 |
196 | # Append sections
197 | APPSEC_EQ = {
198 | }
199 |
200 |
201 | class SaltConverter():
202 | def __init__(self, minput, filters=None):
203 | self.__output = {}
204 | self.__input = copy.deepcopy(minput)
205 | self.__filter = Filter(filters)
206 |
207 | # main method
208 | @GeneralException
209 | def run(self):
210 | Tools.l(INFO, "running", 'run', self)
211 |
212 | #empty imput
213 | if not self.__input:
214 | Tools.l(ERR, "empty input", 'run', self)
215 | return {}
216 |
217 | self.__curent_state = None
218 |
219 | # convert
220 | self.__generate_classes(self.__input)
221 |
222 | Tools.l(INFO, "complete", 'run', self)
223 | return self.__output
224 |
225 | # processing on values
226 | @GeneralException
227 | def __process_values(self, manifest, name, key, val):
228 | if type(val) is int:
229 | val = "%s" % (val)
230 | elif (type(val) is not str) or (not val):
231 | return val
232 | if (manifest in APPSEC_EQ) and (key == APPSEC_EQ[manifest][0]):
233 | val = APPSEC_EQ[manifest][1] + val
234 | return self.__filter.item_replace(manifest, key, val, name)
235 |
236 | # processing on data
237 | # recursive
238 | @GeneralException
239 | def __structure_data(self, data, manifest, name):
240 | Tools.l(INFO, "building data structure", 'structure_data', self)
241 | for key in data:
242 | if type(data[key]) is dict:
243 | data[key] = self.__structure_data(data[key], manifest, name)
244 | elif type(data[key]) is list:
245 | store = []
246 | for d in data[key]:
247 | store.append(self.__process_values(manifest, name, key, d))
248 | data[key] = store
249 | else:
250 | data[key] = self.__process_values(manifest, name, key, data[key])
251 | Tools.l(INFO, "building data structure complete", 'structure_data', self)
252 | return data
253 |
254 | # processing on data
255 | @GeneralException
256 | def __process_data(self, data, manifest, name):
257 | Tools.l(INFO, "processing data", 'process_data', self)
258 | sec_key = (self.__curent_state if type(SECTION_EQ[manifest]) is dict else manifest)
259 |
260 | # modifications
261 | kcontent = Tools.list_merging(AVOIDSEC_EQ.get(MAIN_SECTION), AVOIDSEC_EQ.get(sec_key))
262 | for key in kcontent:
263 | if key in data:
264 | data[key] = None
265 | kcontent = Tools.s_dict_merging(CONTENTADD_EQ.get(MAIN_SECTION), CONTENTADD_EQ.get(sec_key), False)
266 | for key in kcontent:
267 | data[key] = kcontent[key]
268 | kcontent = Tools.s_dict_merging(CONTENTKEY_EQ.get(MAIN_SECTION), CONTENTKEY_EQ.get(sec_key), False)
269 | for key in kcontent:
270 | if key in data:
271 | data[kcontent[key]] = Tools.merge_string_list(data.get(kcontent[key]), data.pop(key))
272 | kcontent = Tools.s_dict_merging(CONTENTVAL_EQ.get(MAIN_SECTION), CONTENTVAL_EQ.get(sec_key), False)
273 | for key in kcontent:
274 | if key in data:
275 | if (data[key] == kcontent[key][0]) or (kcontent[key][0] == MAIN_SECTION):
276 | if type(kcontent[key][1]) is str:
277 | data[key] = kcontent[key][1]
278 | else:
279 | data[key] = kcontent[key][1](data[key], data)
280 |
281 | # set action key
282 | kcontent = ACTIONKEY_EQ.get(sec_key)
283 | if type(kcontent) is str:
284 | data[kcontent] = MAIN_SECTION
285 | elif kcontent:
286 | data[kcontent(data)] = MAIN_SECTION
287 |
288 | # main loop
289 | data = self.__structure_data(data, manifest, name)
290 | Tools.l(INFO, "processing data complete", 'process_data', self)
291 | return data
292 |
293 | # ganaration method
294 | @GeneralException
295 | def __generate_classes(self, data):
296 | for manifest in sorted(data):
297 | if manifest not in SECTION_EQ:
298 | Tools.l(INFO, "Ignored unknown class %s" % (manifest), 'generate_classes', self)
299 | continue
300 | Tools.l(INFO, "creating manifest %s" % (manifest), 'generate_classes', self)
301 | for name in sorted(data[manifest]):
302 | if type(SECTION_EQ[manifest]) is dict:
303 | ref = data[manifest][name].get(SECTION_EQ[manifest]['key'])
304 | if not ref:
305 | Tools.l(ERR, "Reference key not found %s" % (SECTION_EQ[manifest]['key']), 'generate_classes', self)
306 | continue
307 | state = SECTION_EQ[manifest].get(ref)
308 | if not state:
309 | Tools.l(ERR, "State not found ref %s, manifest %s"%(ref,manifest), 'generate_classes', self)
310 | continue
311 | else:
312 | state = SECTION_EQ.get(manifest)
313 | if not state:
314 | Tools.l(ERR, "State not found manifest %s"%(manifest), 'generate_classes', self)
315 | continue
316 | if (manifest == 'packages'):
317 | data[manifest][name] = self.__filter.update_package(data[manifest][name], name, 'latest')
318 | self.__output.setdefault(manifest, {})
319 | self.__output[manifest].setdefault(state, {})
320 | self.__curent_state = state
321 | self.__output[manifest][state][name] = self.__process_data(data[manifest][name], manifest, name)
322 |
--------------------------------------------------------------------------------
/pysa/salt/objects.py:
--------------------------------------------------------------------------------
1 | '''
2 | Salt Objects
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import re
24 |
25 | from pysa.exception import *
26 | from pysa.dependencies import *
27 |
28 | from pysa.scanner.actions.utils import get_stat
29 |
30 | CMLABEL="salt"
31 | ILLEGAL_OBJ_CHAR=None#['\W']
32 |
33 | class SaltObjects():
34 | @staticmethod
35 | def salt_file_dir_obj(dr):
36 | # get group, mode and owner
37 | s = get_stat(dr)
38 | #DEBUG
39 | #s = ('root', oct(0777), 'root')
40 | #/DEBUG
41 | return {
42 | 'path' : dr,
43 | 'ensure' : 'directory',
44 | 'name' : dr,
45 | 'group' : s[0],
46 | 'mode' : s[1],
47 | 'owner' : s[2],
48 | }
49 |
50 | @staticmethod
51 | def salt_pkg_manager_obj(package, provider):
52 | return {
53 | 'name' : package,
54 | 'provider' : provider,
55 | 'version' : 'latest',
56 | }
57 |
58 | @staticmethod
59 | def salt_key_mod_obj(state, obj):
60 | key = "%s_%s"%(state,obj)
61 | if not ILLEGAL_OBJ_CHAR: return key
62 | illegal_char = "%s" % ('|'.join(ILLEGAL_OBJ_CHAR))
63 | key = re.sub(illegal_char, "_", key)
64 | return key
65 |
66 |
67 | OBJ_MAKER = {
68 | 'file' : SaltObjects.salt_file_dir_obj,
69 | 'manager' : SaltObjects.salt_pkg_manager_obj,
70 | 'objkey' : SaltObjects.salt_key_mod_obj,
71 | }
72 |
73 | DEPENDENCIES = {
74 | 'hosts' : {},
75 | 'mounts' : {
76 | # 'hosts' : 'Class',
77 | 'mounts' : [
78 | BASED_ON_FIELD, {
79 | 'field' : [SELF_ORDER, {
80 | 'field' : 'name',
81 | }],
82 | 'key' : 'name',
83 | }
84 | ],
85 | },
86 | 'groups' : {
87 | # 'hosts' : 'Class',
88 | },
89 | 'users' : {
90 | # 'hosts' : 'Class',
91 | 'groups' : [
92 | BASED_ON_FIELD, {
93 | 'field' : 'group',
94 | 'key' : 'name',
95 | }
96 | ],
97 | },
98 | # 'dirs' : {
99 | # 'hosts' : 'Class',
100 | # 'dirs' : [
101 | # BASED_ON_FIELD, {
102 | # 'field' : [SELF_ORDER, {
103 | # 'field' : 'path',
104 | # }],
105 | # 'key' : 'path',
106 | # }
107 | # ],
108 | # 'groups' : [
109 | # BASED_ON_FIELD, {
110 | # 'field' : 'group',
111 | # 'key' : 'name',
112 | # }
113 | # ],
114 | # 'users' : [
115 | # BASED_ON_FIELD, {
116 | # 'field' : 'owner',
117 | # 'key' : 'name',
118 | # }
119 | # ],
120 | # 'mounts' : [
121 | # BASED_ON_FIELD, {
122 | # 'field' : [GET_MOUNT_FROM_PATH, {
123 | # 'field' : 'path',
124 | # }],
125 | # 'key' : 'device',
126 | # }
127 | # ],
128 | # },
129 | 'keys' : {
130 | # 'hosts' : 'Class',
131 | 'groups' : [
132 | BASED_ON_FIELD, {
133 | 'field' : 'group',
134 | 'key' : 'name',
135 | }
136 | ],
137 | 'users' : [
138 | BASED_ON_FIELD, {
139 | 'field' : 'owner',
140 | 'key' : 'name',
141 | }
142 | ],
143 | # 'dirs' : [
144 | # BASED_ON_FIELD, {
145 | # 'field' : [GET_BASE_PATH, {
146 | # 'field' : 'path',
147 | # }],
148 | # 'key' : 'path',
149 | # }
150 | # ],
151 | },
152 | 'repos' : {
153 | # 'hosts' : 'Class',
154 | 'groups' : [
155 | BASED_ON_FIELD, {
156 | 'field' : 'group',
157 | 'key' : 'name',
158 | }
159 | ],
160 | 'users' : [
161 | BASED_ON_FIELD, {
162 | 'field' : 'owner',
163 | 'key' : 'name',
164 | }
165 | ],
166 | # 'dirs' : [
167 | # BASED_ON_FIELD, {
168 | # 'field' : [GET_BASE_PATH, {
169 | # 'field' : 'path',
170 | # }],
171 | # 'key' : 'path',
172 | # }
173 | # ],
174 | },
175 | 'packages' : {
176 | # 'hosts' : 'Class',
177 | 'repos' : [
178 | BASED_ON_FIELD, {
179 | 'field' : 'provider',
180 | 'key' : 'provider',
181 | }
182 | ],
183 | 'packages' : [
184 | PACKAGE_MANAGER, {
185 | 'field' : 'provider',
186 | 'key' : None,
187 | }
188 | ],
189 | # 'dirs' : [
190 | # BASED_ON_FIELD, {
191 | # 'field' : [GET_BASE_PATH, {
192 | # 'field' : 'path',
193 | # }],
194 | # 'key' : 'path',
195 | # }
196 | # ],
197 | },
198 | 'files' : {
199 | # 'hosts' : 'Class',
200 | 'groups' : [
201 | BASED_ON_FIELD, {
202 | 'field' : 'group',
203 | 'key' : 'name',
204 | }
205 | ],
206 | 'users' : [
207 | BASED_ON_FIELD, {
208 | 'field' : 'owner',
209 | 'key' : 'name',
210 | }
211 | ],
212 | # 'dirs' : [
213 | # BASED_ON_FIELD, {
214 | # 'field' : [GET_BASE_PATH, {
215 | # 'field' : 'path',
216 | # }],
217 | # 'key' : 'path',
218 | # }
219 | # ],
220 | 'packages' : [
221 | BASED_ON_FIELD, {
222 | 'field' : 'path',
223 | 'key' : 'config_files',
224 | }
225 | ],
226 | },
227 | 'crons' : {
228 | # 'hosts' : 'Class',
229 | 'users' : [
230 | BASED_ON_FIELD, {
231 | 'field' : 'user',
232 | 'key' : 'name',
233 | }
234 | ],
235 | },
236 | 'sources' : {
237 | # 'hosts' : 'Class',
238 | 'sources' : [
239 | BASED_ON_FIELD, {
240 | 'field' : [SELF_ORDER, {
241 | 'field' : 'path',
242 | }],
243 | 'key' : 'path',
244 | }
245 | ],
246 | 'groups' : [
247 | BASED_ON_FIELD, {
248 | 'field' : 'group',
249 | 'key' : 'name',
250 | }
251 | ],
252 | 'users' : [
253 | BASED_ON_FIELD, {
254 | 'field' : 'owner',
255 | 'key' : 'name',
256 | }
257 | ],
258 | # 'dirs' : [
259 | # BASED_ON_FIELD, {
260 | # 'field' : [GET_BASE_PATH, {
261 | # 'field' : 'path',
262 | # }],
263 | # 'key' : 'path',
264 | # }
265 | # ],
266 | 'keys' : [
267 | BASED_ON_FIELD, {
268 | 'field' : 'key',
269 | 'key' : 'name',
270 | }
271 | ],
272 | 'packages' : [
273 | BASED_ON_FIELD, {
274 | 'field' : [GET_PKG_FROM_SCM, {
275 | 'field' : 'scm',
276 | }],
277 | 'key' : 'name',
278 | }
279 | ],
280 | },
281 | 'services' : {
282 | # how to identify services?
283 | },
284 | }
285 |
--------------------------------------------------------------------------------
/pysa/scanner/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/scanner/__init__.py
--------------------------------------------------------------------------------
/pysa/scanner/actions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/scanner/actions/__init__.py
--------------------------------------------------------------------------------
/pysa/scanner/actions/base.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-27
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | import subprocess
24 | import logging
25 | import re
26 |
27 | from pysa.exception import *
28 | from pysa.scanner.object.package import Package
29 | from pysa.scanner.object.file import File
30 | from pysa.scanner.object.user import User
31 | from pysa.scanner.object.service import Service
32 | from pysa.scanner.object.repository import Repository
33 | from pysa.scanner.object.group import Group
34 | from pysa.scanner.object.cron import Cron
35 | from pysa.scanner.object.host import Host
36 | from pysa.scanner.object.mount import Mount
37 | from pysa.scanner.object.sshkey import SSHKey
38 | from pysa.scanner.object.source import Source
39 | from pysa.scanner.object.process import Process
40 |
41 |
42 | class ScannerBase():
43 | def __init__(self, packages, files, crons, groups, mounts, hosts, repos, services, sshkeys, users, ips, sources, proces):
44 | self.packages = packages
45 | self.files = files
46 | self.crons = crons
47 | self.groups = groups
48 | self.mounts = mounts
49 | self.hosts = hosts
50 | self.repos = repos
51 | self.services = services
52 | self.keys = sshkeys
53 | self.users = users
54 | self.ips = ips
55 | self.sources = sources
56 | self.proces = proces
57 |
58 | def scan(self):
59 | '''
60 | Implement in ScannerYum, ScannerApt...
61 | '''
62 | pass
63 |
64 | @GeneralException
65 | def subprocess(self, command):
66 | logging.info('ScannerBase.subprocess(): Command, %s' % str(command))
67 | try:
68 | # redirect the err to /dev/null
69 | devnull = open('/dev/null', 'w')
70 | p = subprocess.Popen(
71 | command,
72 | close_fds=True,
73 | stdout=subprocess.PIPE,
74 | stderr=devnull
75 | )
76 | return self.__generator(p.stdout)
77 | except OSError:
78 | logging.debug("ScannerBase.subprocess(): commnand failed, command: %s" % str(command))
79 | return
80 |
81 | @GeneralException
82 | def __generator(self, stdout):
83 | for line in stdout:
84 | yield line
85 |
86 | @GeneralException
87 | def add_package(self, *args, **kargs):
88 | _package = Package(*args, **kargs)
89 |
90 | if self.rules:
91 | # attribute filter
92 | if _package.attr_filter(self.rules['discard']): return
93 | # additional filter
94 | _package.add_filter(self.rules['addition'])
95 |
96 | self.packages[_package.primaryvalue] = _package.prase()
97 |
98 | @GeneralException
99 | def get_packages(self):
100 | return self.packages
101 |
102 | @GeneralException
103 | def add_file(self, *args, **kargs):
104 | _file = File(*args, **kargs)
105 |
106 | if self.rules:
107 | if _file.attr_filter(self.rules['discard']): return
108 | _file.add_filter(self.rules['addition'])
109 |
110 | self.files[_file.primaryvalue] = _file.prase()
111 |
112 | @GeneralException
113 | def get_files(self):
114 | return self.files
115 |
116 | @GeneralException
117 | def add_user(self, *args, **kargs):
118 | _user = User(*args, **kargs)
119 |
120 | if self.rules:
121 | if _user.attr_filter(self.rules['discard']): return
122 | _user.add_filter(self.rules['addition'])
123 |
124 | self.users[_user.primaryvalue] = _user.prase()
125 |
126 | @GeneralException
127 | def get_users(self):
128 | return self.users
129 |
130 | @GeneralException
131 | def add_service(self, *args, **kargs):
132 | _service = Service(*args, **kargs)
133 |
134 | if self.rules:
135 | if _service.attr_filter(self.rules['discard']): return
136 | _service.add_filter(self.rules['addition'])
137 |
138 | self.services[_service.primaryvalue] = _service.prase()
139 |
140 | @GeneralException
141 | def get_services(self):
142 | return self.services
143 |
144 | @GeneralException
145 | def add_repo(self, *args, **kargs):
146 | _repo = Repository(*args, **kargs)
147 |
148 | if self.rules:
149 | if _repo.attr_filter(self.rules['discard']): return
150 | _repo.add_filter(self.rules['addition'])
151 |
152 | self.repos[_repo.primaryvalue] = _repo.prase()
153 |
154 | @GeneralException
155 | def get_repos(self):
156 | return self.repos
157 |
158 | @GeneralException
159 | def add_group(self, *args, **kargs):
160 | _group = Group(*args, **kargs)
161 |
162 | if self.rules:
163 | if _group.attr_filter(self.rules['discard']): return
164 | _group.add_filter(self.rules['addition'])
165 |
166 | self.groups[_group.primaryvalue] = _group.prase()
167 |
168 | @GeneralException
169 | def get_groups(self):
170 | return self.groups
171 |
172 | @GeneralException
173 | def add_cron(self, *args, **kargs):
174 | _cron = Cron(*args, **kargs)
175 |
176 | if self.rules:
177 | if _cron.attr_filter(self.rules['discard']): return
178 | _cron.add_filter(self.rules['addition'])
179 |
180 | self.crons[_cron.primaryvalue] = _cron.prase()
181 |
182 | @GeneralException
183 | def get_crons(self):
184 | return self.crons
185 |
186 | @GeneralException
187 | def add_host(self, *args, **kargs):
188 | _host = Host(*args, **kargs)
189 |
190 | if self.rules:
191 | if _host.attr_filter(self.rules['discard']): return
192 | _host.add_filter(self.rules['addition'])
193 |
194 | self.hosts[_host.primaryvalue] = _host.prase()
195 |
196 | @GeneralException
197 | def get_hosts(self):
198 | return self.hosts
199 |
200 | @GeneralException
201 | def add_mount(self, *args, **kargs):
202 | _mount = Mount(*args, **kargs)
203 |
204 | if self.rules:
205 | if _mount.attr_filter(self.rules['discard']): return
206 | _mount.add_filter(self.rules['addition'])
207 |
208 | self.mounts[_mount.primaryvalue] = _mount.prase()
209 |
210 | @GeneralException
211 | def get_mounts(self):
212 | return self.mounts
213 |
214 | @GeneralException
215 | def add_key(self, *args, **kargs):
216 | _key = SSHKey(*args, **kargs)
217 |
218 | if self.rules:
219 | if _key.attr_filter(self.rules['discard']): return
220 | _key.add_filter(self.rules['addition'])
221 |
222 | self.keys[_key.primaryvalue] = _key.prase()
223 |
224 | @GeneralException
225 | def get_keys(self):
226 | return self.keys
227 |
228 | @GeneralException
229 | def add_ip(self, mip):
230 | self.ips.append(mip)
231 |
232 | @GeneralException
233 | def get_ips(self):
234 | return self.ips
235 |
236 | @GeneralException
237 | def add_source(self, *args, **kargs):
238 | _source = Source(*args, **kargs)
239 |
240 | if self.rules:
241 | if _source.attr_filter(self.rules['discard']): return
242 | _source.add_filter(self.rules['addition'])
243 |
244 | self.sources[_source.primaryvalue] = _source.prase()
245 |
246 | @GeneralException
247 | def get_sources(self):
248 | return self.sources
249 |
250 | @GeneralException
251 | def add_proc(self, *args, **kargs):
252 | _process = Process(*args, **kargs)
253 |
254 | if self.rules:
255 | if _process.attr_filter(self.rules['discard']): return
256 | _process.add_filter(self.rules['addition'])
257 |
258 | self.proces[_process.primaryvalue] = _process.prase()
259 |
260 | @GeneralException
261 | def get_proces(self):
262 | return self.proces
263 |
264 | @GeneralException
265 | def init_filter(self, rules=None):
266 | """
267 | init the filter rules
268 | """
269 |
270 | self.rules = rules
271 |
272 | if self.rules: # init the discard and addition rules if not
273 | if 'discard' not in self.rules: self.rules['discard'] = {}
274 | if 'addition' not in self.rules: self.rules['addition'] = {}
275 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/cron.py:
--------------------------------------------------------------------------------
1 | '''
2 | Scan cron files
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import logging
24 | import os
25 |
26 | from pysa.scanner.actions.base import ScannerBase
27 |
28 |
29 | class ScannerCron(ScannerBase):
30 | def scan(self):
31 |
32 | users = self.get_users()
33 | res = self.subprocess(['crontab', '-l'])
34 | for line in res:
35 | # ignore empty and the comment lines
36 | if not line.strip() or line.strip().startswith("#"): continue
37 |
38 | ary = line.split()
39 | if ary[5] in users.keys():
40 | paths = os.path.split(ary[6])
41 | self.add_cron(
42 | name=paths[1],
43 | command=" ".join(ary[6:]),
44 | environment=paths[0],
45 | user=ary[5],
46 | minute=ary[0],
47 | hour=ary[1],
48 | monthday=ary[2],
49 | month=ary[3],
50 | weekday=ary[4]
51 | )
52 | else:
53 | paths = os.path.split(ary[5])
54 | self.add_cron(
55 | name=paths[1],
56 | command=" ".join(ary[5:]),
57 | environment=paths[0],
58 | minute=ary[0],
59 | hour=ary[1],
60 | monthday=ary[2],
61 | month=ary[3],
62 | weekday=ary[4]
63 | )
64 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/file.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-31
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import os
24 | import re
25 | import logging
26 |
27 | from pysa.config import *
28 | from pysa.scanner.actions.utils import *
29 | from pysa.scanner.actions.base import ScannerBase
30 |
31 |
32 | class ScannerFile(ScannerBase):
33 |
34 | suf_list = ['.conf', '.cfg', '.ini']
35 |
36 | def scan(self):
37 | """
38 | scan config files
39 | """
40 |
41 | logging.info('searching for config files')
42 |
43 | # scan the system directories
44 | self.scandir(Config.files_path)
45 |
46 | # scan directory and add config files
47 | def scandir(self, pathdir):
48 | # Visit every file in pathdir except those on the exclusion list above.
49 | pathdirs = re.split(":", pathdir)
50 | for p in pathdirs:
51 | if not p: continue
52 | for dirpath, dirnames, filenames in os.walk(p, followlinks=True):
53 | for filename in filenames:
54 | self.addfile(os.path.join(dirpath, filename))
55 |
56 | # add per config file
57 | def addfile(self, pathname):
58 | # only plane text file
59 | if valid_txtfile(pathname) == False:
60 | return
61 |
62 | # # only include above suffix config file
63 | # suf = os.path.splitext(pathname)[1]
64 | # if suf is None or suf not in self.suf_list:
65 | # return
66 |
67 | # get owner, group and mode
68 | s = get_stat(pathname)
69 |
70 | # read the config file's content
71 | c = get_content(pathname)
72 |
73 | # add the config file:
74 | # checksum, content, group, mode, owner, path, force=False, provider=None,
75 | # recurse=None, recurselimit=None, source=None
76 | self.add_file('md5', c, s[0], s[1], s[2], pathname)
77 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/gem.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-29
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import glob
24 | import logging
25 | import re
26 | import os
27 |
28 | from pysa.scanner.actions.utils import *
29 | from pysa.scanner.actions.base import ScannerBase
30 |
31 | class ScannerGem(ScannerBase):
32 |
33 | def scan(self):
34 | """
35 | scan gem
36 | """
37 | logging.info('searching for Ruby gems')
38 |
39 | # Precompile a pattern for extracting the version of Ruby that was used
40 | # to install the gem.
41 | pattern = re.compile(r'gems/([^/]+)/gems')
42 |
43 | # Look for gems in all the typical places. This is easier than looking
44 | # for `gem` commands, which may or may not be on `PATH`.
45 | for globname in ('/usr/lib/ruby/gems/*/gems',
46 | '/usr/lib64/ruby/gems/*/gems',
47 | '/usr/local/lib/ruby/gems/*/gems',
48 | '/var/lib/gems/*/gems'):
49 | for dirname in glob.glob(globname):
50 | # The `ruby1.9.1` (really 1.9.2) package on Maverick begins
51 | # including RubyGems in the `ruby1.9.1` package and marks the
52 | # `rubygems1.9.1` package as virtual. So for Maverick and
53 | # newer, the manager is actually `ruby1.9.1`.
54 | match = pattern.search(dirname)
55 | if '1.9.1' == match.group(1) and rubygems_virtual():
56 | manager = 'ruby{0}'.format(match.group(1))
57 |
58 | # Oneiric and RPM-based distros just have one RubyGems package.
59 | elif rubygems_unversioned():
60 | manager = 'rubygems'
61 |
62 | # Debian-based distros qualify the package name with the version
63 | # of Ruby it will use.
64 | else:
65 | manager = 'rubygems{0}'.format(match.group(1))
66 |
67 | for entry in os.listdir(dirname):
68 | try:
69 | package, version = entry.rsplit('-', 1)
70 |
71 | except ValueError:
72 | logging.warning('skipping questionably named gem {0}'.
73 | format(entry))
74 | continue
75 |
76 | self.add_package(package, manager=manager, version=version, provider='gem')
77 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/group.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-4-4
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | import logging
24 | import grp
25 |
26 | from pysa.scanner.actions.base import ScannerBase
27 |
28 |
29 | class ScannerGroup(ScannerBase):
30 |
31 | def scan(self):
32 | for g in grp.getgrall():
33 | name, password, gid, member = g
34 | self.add_group(name=name, gid=gid, member=member)
35 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/host.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-03
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import re
24 | import logging
25 | import os
26 |
27 | from pysa.config import *
28 | from pysa.scanner.actions.base import ScannerBase
29 |
30 |
31 | class ScannerHost(ScannerBase):
32 |
33 | def scan(self):
34 | """
35 | scan host:
36 | parse the host config file (usually /etc/hosts)
37 | """
38 | logging.info('searching for hosts')
39 | hostlst = self.parse_hostfile()
40 |
41 | if len(hostlst)<=0:
42 | return
43 |
44 | for dict in hostlst:
45 | self.add_host(ip=dict['ip'], name=dict['name'],
46 | target=dict['target'], host_aliases=dict['host_aliases'])
47 |
48 | def parse_hostfile(self):
49 | hostfile = Config.scan_host
50 |
51 | hosts = []
52 | try:
53 | for line in open(hostfile):
54 | # ignore blank line
55 | if not line.strip(): continue
56 | # ignore comment line
57 | elif line.strip().startswith("#"): continue
58 |
59 | itemlst = line.strip().split()
60 | if len(itemlst) <= 1: continue
61 |
62 | hosts.append({'ip':itemlst[0], 'name':itemlst[1], 'target':hostfile, 'host_aliases':itemlst[2:]})
63 |
64 | #ip = re.search( r'[0-9]+(?:\.[0-9]+){3}', itemlst[0] )
65 | #if ip==None:
66 | # continue
67 | #aliases = []
68 | #if len(itemlst)>=3:
69 | # for i in range(2, len(itemlst)-1):
70 | # aliases.append(itemlst[i])
71 | #print "aliases=%s"%aliases
72 | #hosts.append({'ip':ip.group(), 'name':itemlst[1], 'target':hostfile, 'host_aliases':aliases})
73 |
74 | except IOError:
75 | return hosts
76 |
77 | return hosts
78 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/mount.py:
--------------------------------------------------------------------------------
1 | '''
2 | Scans mount points
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import logging
24 | import os
25 |
26 | from pysa.scanner.actions.base import ScannerBase
27 |
28 |
29 | class ScannerMount(ScannerBase):
30 |
31 | def scan(self):
32 | self.__disk_usage()
33 |
34 | def __disk_partitions(self,all=False):
35 | """
36 | Return all mountd partitions as a dict.
37 | """
38 | phydevs = []
39 | f = open("/proc/filesystems", "r")
40 | for line in f:
41 | if not line.startswith("nodev"):
42 | phydevs.append(line.strip())
43 |
44 | retlist = {}
45 | f = open('/etc/mtab', "r")
46 | for line in f:
47 | if not all and line.startswith('none'):
48 | continue
49 | fields = line.split()
50 | device = fields[0]
51 | mountpoint = fields[1]
52 | fstype = fields[2]
53 | if not all and fstype not in phydevs:
54 | continue
55 | if device == 'none':
56 | device = ''
57 | retlist[device] = (device, mountpoint, fstype)
58 | return retlist
59 |
60 | def __disk_usage(self):
61 | """Return disk usage associated with path."""
62 | disk = self.__disk_partitions()
63 | for device in disk.keys():
64 | st = os.statvfs(device)
65 | size = st.f_blocks * st.f_frsize
66 | self.add_mount(device=device, fstype=disk[device][2], name=disk[device][1], size=size)
67 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/npm.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-29
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import re
24 | import logging
25 |
26 | from pysa.scanner.actions.base import ScannerBase
27 |
28 |
29 | class ScannerNpm(ScannerBase):
30 | def scan(self):
31 | """
32 | scan apt
33 | """
34 | logging.info('searching for npm packages')
35 |
36 | # Precompile a pattern for parsing the output of `npm list -g`.
37 | pattern = re.compile(r'^\S+ (\S+)@(\S+)$')
38 |
39 | lines = self.subprocess(['npm', 'ls', '-g']) # only list global packages
40 | for line in lines:
41 | match = pattern.match(line.rstrip())
42 | if match is None:
43 | continue
44 | package, version = match.group(1), match.group(2)
45 | manager='nodejs'
46 |
47 | self.add_package(package, manager=manager, version=version, provider='npm')
48 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/php.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-29
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import logging
24 | import re
25 |
26 | from pysa.scanner.actions.utils import *
27 | from pysa.scanner.actions.base import ScannerBase
28 |
29 |
30 | class ScannerPhp(ScannerBase):
31 | def scan(self):
32 | """
33 | scan php
34 | """
35 | logging.info('searching for PEAR/PECL packages')
36 |
37 | # Precompile a pattern for parsing the output of `{pear,pecl} list`.
38 | pattern = re.compile(r'^([0-9a-zA-Z_]+)\s+([0-9][0-9a-zA-Z\.-]*)\s')
39 |
40 | # PEAR packages are managed by `php-pear` (obviously). PECL packages
41 | # are managed by `php5-dev` because they require development headers
42 | # (less obvious but still makes sense).
43 | if lsb_release_codename() is None:
44 | pecl_manager = 'php-devel'
45 | else:
46 | pecl_manager = 'php5-dev'
47 | for manager, progname in (('php-pear', 'pear'),
48 | (pecl_manager, 'pecl')):
49 |
50 | lines = self.subprocess([progname, 'list'])
51 | if lines==None:
52 | return
53 |
54 | for line in lines:
55 | match = pattern.match(line)
56 | if match is None:
57 | continue
58 | package, version = match.group(1), match.group(2)
59 |
60 | self.add_package(package, manager=manager, version=version, provider=progname)
61 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/process.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-19
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import logging
24 | import subprocess
25 |
26 | from pysa.scanner.actions.base import ScannerBase
27 |
28 |
29 | class ScannerProcess(ScannerBase):
30 |
31 | def scan(self):
32 | """
33 | scan process
34 | """
35 | logging.info('searching for process')
36 |
37 | self.get_processes()
38 |
39 | def get_processes(self):
40 | """
41 | get all the process info
42 | """
43 |
44 | try:
45 | p = subprocess.Popen(['-c', 'ps -eo pid,fuser,s,pcpu,pmem,comm,ppid'], shell=True, stdout=subprocess.PIPE)
46 |
47 | first = True
48 | for line in p.stdout:
49 | if first: # ignore the headline
50 | first = False
51 | continue
52 |
53 | lst = line.strip().split()
54 |
55 | # check data completeness
56 | nonlst = [info for info in lst if info is None]
57 | if len(nonlst)>0: continue
58 |
59 | self.add_proc(lst[0], lst[1], lst[2], lst[3], lst[4], lst[5], lst[6])
60 |
61 | except OSError:
62 | return
63 |
64 | def get_accounts(self):
65 | """
66 | parse the system accounts config file('/etc/passwd')
67 | """
68 |
69 | try:
70 | data = open('/etc/passwd').read()
71 | for line in data:
72 | att = line.strip().split(':')
73 | if att[0] is not None and att[2] is not None:
74 | self.add_accout(att[2], att[0])
75 | except IOError:
76 | return False
77 |
78 | return True
79 |
80 | @property
81 | def accounts(self):
82 | if accounts not in self:
83 | self.accounts = {}
84 |
85 | return self.accounts
86 |
87 | def add_accout(self, uid, name):
88 | if uid not in self.accounts:
89 | self.accounts[uid] = name
90 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/pypi.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-29
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import glob
24 | import logging
25 | import os
26 | import re
27 | import subprocess
28 |
29 | from pysa.scanner.actions.base import ScannerBase
30 |
31 |
32 | # Precompile a pattern to extract the manager from a pathname.
33 | PATTERN_MANAGER = re.compile(r'lib/(python[^/]*)/(dist|site)-packages')
34 |
35 | # Precompile patterns for differentiating between packages built by
36 | # `easy_install` and packages built by `pip`.
37 | PATTERN_EGG = re.compile(r'\.egg$')
38 | PATTERN_EGGINFO = re.compile(r'\.egg-info$')
39 |
40 | # Precompile a pattern for extracting package names and version numbers.
41 | PATTERN = re.compile(r'^([^-]+)-([^-]+).*\.egg(-info)?$')
42 |
43 | class ScannerPypi(ScannerBase):
44 |
45 | def scan(self):
46 | """
47 | scan pypi
48 | """
49 | logging.info('searching for Python packages')
50 |
51 | # Look for packages in the typical places.
52 | globnames = ['/usr/lib/python*/dist-packages',
53 | '/usr/lib/python*/site-packages',
54 | '/usr/local/lib/python*/dist-packages',
55 | '/usr/local/lib/python*/site-packages']
56 | virtualenv = os.getenv('VIRTUAL_ENV')
57 | if virtualenv is not None:
58 | globnames.extend(['{0}/lib/python*/dist-packages'.format(virtualenv),
59 | '{0}/lib/python*/dist-packages'.format(virtualenv)])
60 | for globname in globnames:
61 | for dirname in glob.glob(globname):
62 | manager = PATTERN_MANAGER.search(dirname).group(1)
63 | for entry in os.listdir(dirname):
64 | match = PATTERN.match(entry)
65 | if match is None:
66 | continue
67 | package, version = match.group(1, 2)
68 |
69 | pathname = os.path.join(dirname, entry)
70 |
71 | # Symbolic links indicate this is actually a system package
72 | # that injects files into the PYTHONPATH.
73 | if os.path.islink(pathname): continue
74 |
75 | # check pathname
76 | if not os.path.isdir(pathname):
77 | pathname = os.path.join(dirname, package)
78 | if not os.path.exists(pathname): continue
79 |
80 | # installed via `easy_install`.
81 | if PATTERN_EGG.search(entry):
82 | self.add_package(package, manager='python', version=version, provider='pip')
83 |
84 | # installed via `pip`.
85 | elif PATTERN_EGGINFO.search(entry):
86 | self.add_package(package, manager='pip', version=version, provider='pip')
87 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/repository.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-18
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import os
24 | import logging
25 |
26 | from pysa.scanner.actions.utils import *
27 | from pysa.scanner.actions.base import ScannerBase
28 |
29 |
30 | class ScannerRepo(ScannerBase):
31 |
32 | def scan(self):
33 | """
34 | scan for repository config files
35 | """
36 | logging.info('searching for repository config files')
37 |
38 | if os.path.exists('/etc/yum.repos.d'):
39 | self.scan_yum()
40 | elif os.path.exists('/etc/apt'):
41 | self.scan_apt()
42 |
43 | def scan_yum(self):
44 | """
45 | scan yum repo config files
46 | """
47 |
48 | for dirpath, dirnames, files in os.walk('/etc/yum.repos.d'):
49 | for file in files:
50 | root, ext = os.path.splitext(file)
51 | if ext!='.repo': continue
52 |
53 | self.addfile(os.path.join(dirpath, file), 'yum')
54 |
55 | def scan_apt(self):
56 | """
57 | scan apt repo config files
58 | """
59 |
60 | try:
61 | with open('/etc/apt/sources.list'): self.addfile('/etc/apt/sources.list', 'apt')
62 | except IOError:
63 | return
64 |
65 | if os.path.exists('/etc/apt/sources.list.d'):
66 | for dirpath, dirnames, files in os.walk('/etc/apt/sources.list.d'):
67 | for file in files:
68 | root, ext = os.path.splitext(file)
69 | if ext!='.list': continue
70 |
71 | self.addfile(os.path.join(dirpath, file), 'apt')
72 |
73 |
74 | # add per config file
75 | def addfile(self, pathname, prov):
76 | # only plane text file
77 | if valid_txtfile(pathname) == False:
78 | return
79 |
80 | # get owner, group and mode
81 | s = get_stat(pathname)
82 |
83 | # read the config file's content
84 | c = get_content(pathname)
85 |
86 | # add the config file:
87 | # checksum, content, group, mode, owner, path, force=False, provider=None,
88 | # recurse=None, recurselimit=None, source=None
89 | self.add_repo('md5', c, s[0], s[1], s[2], pathname, provider=prov)
90 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/service.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-03
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import re
24 | import logging
25 | import os
26 |
27 | from pysa.config import *
28 | from pysa.scanner.actions.base import ScannerBase
29 |
30 |
31 | class ScannerService(ScannerBase):
32 | # Patterns for determining which Upstart services should be included, based
33 | # on the events used to start them.
34 | UPSTART_PATTERN1 = re.compile(r'start\s+on\s+runlevel\s+\[[2345]', re.S)
35 | UPSTART_PATTERN2 = re.compile(r'start\s+on\s+\([^)]*(?:filesystem|filesystems|local-filesystems|mounted|net-device-up|remote-filesystems|startup|virtual-filesystems)[^)]*\)', re.S)
36 |
37 | def scan(self):
38 | """
39 | scan service:
40 | search the service config files
41 | """
42 | logging.info('searching for system services')
43 |
44 | for dir in ['/etc/init',
45 | '/etc/init.d',
46 | '/etc/rc.d/init.d']:
47 | for dirname, dirnames, filenames in os.walk(dir):
48 | for filename in filenames:
49 | try:
50 | pathname = os.path.join(dirname, filename)
51 | dict = self.parse_service(pathname)
52 |
53 | # add service
54 | if dict != None:
55 | # format (provider, name, enable, hasrestart)
56 | self.add_service(enable=dict['enable'], hasrestart=dict['hasrestart'],
57 | name=dict['name'], path=dirname, provider=dict['provider'], hasstatus=dict['hasstatus'])
58 | except ValueError:
59 | pass
60 |
61 | def parse_service(self, pathname):
62 | """
63 | Parse a potential service init script or config file into the
64 | manager and service name or raise `ValueError`. Use the Upstart
65 | "start on" stanzas and SysV init's LSB headers to restrict services to
66 | only those that start at boot and run all the time.
67 |
68 | ###Need to add systemd service parse.
69 | """
70 |
71 | dirname, basename = os.path.split(pathname)
72 | if '/etc/init' == dirname:
73 | service, ext = os.path.splitext(basename)
74 |
75 | # Ignore extraneous files in /etc/init.
76 | if '.conf' != ext:
77 | raise ValueError('not an Upstart config')
78 |
79 | # Ignore services that don't operate on the (faked) main runlevels.
80 | try:
81 | content = open(pathname).read()
82 | except IOError:
83 | raise ValueError('not a readable Upstart config')
84 |
85 | enable = False
86 | if (self.UPSTART_PATTERN1.search(content) \
87 | or self.UPSTART_PATTERN2.search(content)):
88 | enable = True
89 |
90 | return {'provider':'upstart', 'name':service, 'enable':enable,
91 | 'hasrestart':False, 'hasstatus':False}
92 |
93 | elif '/etc/init.d' == dirname or '/etc/rc.d/init.d' == dirname:
94 | #import pdb
95 | #pdb.set_trace()
96 |
97 | # Let Upstart handle its services.
98 | if os.path.islink(pathname) \
99 | and '/lib/init/upstart-job' == os.readlink(pathname):
100 | raise ValueError('proxy for an Upstart config')
101 |
102 | # Ignore services that don't operate on the main runlevels.
103 | try:
104 | content = open(pathname).read()
105 | except IOError:
106 | raise ValueError('not a readable SysV init script')
107 |
108 | enable = False
109 | if re.search(r'(?:Default-Start|chkconfig):\s*[-2345]', content):
110 | enable = True
111 |
112 | hasrestart = False
113 | if re.search(r'\s*(?:restart|reload)\)\s*', content):
114 | hasrestart = True
115 |
116 | hasstatus = False
117 | if re.search(r'\s*status\)\s*', content):
118 | hasstatus = True
119 |
120 | return {'provider':'init', 'name':basename, 'enable':enable,
121 | 'hasrestart':hasrestart, 'hasstatus':hasstatus} ### change sysvinit to init
122 | else:
123 | raise ValueError('not a service')
124 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/source.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-09
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import os
24 | import logging
25 | import subprocess
26 | from collections import defaultdict
27 |
28 | from pysa.scanner.actions.utils import *
29 | from pysa.scanner.actions.base import ScannerBase
30 |
31 |
32 | class ScannerSource(ScannerBase):
33 |
34 | def scan(self):
35 | """
36 | scan repository
37 | """
38 | logging.info('searching for repository')
39 |
40 | self.user_repos = {'git':[], 'svn':[], 'hg':[]} # save all the scm repos
41 |
42 | self.search_scm('/')
43 | #self.search_scm('svn', '/')
44 | #self.search_scm('hg', '/')
45 | self.add_repos()
46 |
47 | def search_scm(self, dir):
48 | """
49 | search all scm repositories in local directory dirname
50 | """
51 | for dirpath, dirnames, filenames in os.walk(dir):
52 | for dirname in dirnames:
53 | if dirname == '.git':
54 | scm = 'git'
55 | elif dirname == '.svn':
56 | scm = 'svn'
57 | elif dirname == '.hg':
58 | scm = 'hg'
59 | else:
60 | continue
61 |
62 | if scm=='svn': #if svn scm, need to check subdirctory
63 | head, tail = os.path.split(dirpath)
64 | while head and tail:
65 | if head in self.user_repos['svn']:
66 | break
67 | head, tail = os.path.split(head)
68 | else:
69 | subsvn = [ scmdir for scmdir in self.user_repos['svn'] if dirpath in scmdir ]
70 | if len(subsvn)>0:
71 | for subdir in subsvn: self.user_repos['svn'].remove(subdir)
72 |
73 | self.add_local_repo(scm, dirpath)
74 | else:
75 | self.add_local_repo(scm, dirpath)
76 |
77 | def add_repos(self):
78 | """
79 | get the repository info and add repo
80 | """
81 | for (scm, dirs) in self.user_repos.items():
82 | for dirname in dirs:
83 | sources = [] # http/ssh
84 | branches = []
85 |
86 | try:
87 | if scm=='git':
88 | p = subprocess.Popen(['-c', 'git --git-dir=' + dirname + '/.git --work-tree=' + dirname+' remote -v'],
89 | stdout=subprocess.PIPE, shell=True)
90 | for line in p.stdout:
91 | src = line.split('\t')[1].split(' ')[0]
92 | if src is not None and src not in sources:
93 | sources.append(src)
94 |
95 | # branches
96 | p = subprocess.Popen(['-c', 'git --git-dir=' + dirname + '/.git --work-tree=' + dirname + ' branch'],
97 | stdout=subprocess.PIPE, shell=True)
98 | for line in p.stdout:
99 | if line is not None:
100 | lst = line.strip().split()
101 | for br in lst:
102 | if br is not None and br!='*':
103 | branches.append(br)
104 |
105 | elif scm=='svn':
106 | p = subprocess.Popen(['-c', 'svn info ' + dirname + ' | grep URL | awk \'{print $NF}\''],
107 | shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
108 |
109 | for source in p.stdout:
110 | if source is not None: sources.append(source.strip())
111 |
112 | ### branches
113 | elif scm=='hg':
114 | # sources
115 | p = subprocess.Popen(['-c', 'hg paths -R' + dirname], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
116 | for line in p.stdout:
117 | source = line.split('=')[1].strip()
118 | if source is not None and source not in sources: sources.append(source)
119 | # branches
120 | p = subprocess.Popen(['-c', 'hg branches -R' + dirname], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
121 | for line in p.stdout:
122 | branch = line.strip().split(' ')[0]
123 | if branch is not None and branch not in branches: branches.append(branch)
124 | else:
125 | continue
126 |
127 | except OSError:
128 | continue
129 |
130 | # add the public info
131 | s = get_stat(dirname)
132 |
133 | if len(sources)<=0: continue
134 |
135 | self.add_source(sources[0], os.path.basename(dirname), dirname, s[2], s[0], s[1], scm, branches)
136 |
137 | @property
138 | def user_repos(self):
139 | """
140 | local repos
141 | """
142 | if user_repos not in self:
143 | self.user_repos = defaultdict(list)
144 |
145 | return self.user_repos
146 |
147 | def add_local_repo(self, scm, dirname):
148 | """
149 | add all repos to local repos
150 | """
151 | if scm not in self.user_repos:
152 | self.user_repos[scm] = []
153 |
154 | self.user_repos[scm].append(dirname)
155 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/sshkey.py:
--------------------------------------------------------------------------------
1 | '''
2 | Get SSH keys
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | import logging
24 | import os
25 | import re
26 |
27 | from pysa.config import Config
28 | from pysa.scanner.actions.base import ScannerBase
29 | from pysa.scanner.actions.utils import *
30 |
31 |
32 | class ScannerKey(ScannerBase):
33 |
34 | support_key_type = [
35 | 'ssh-rsa',
36 | 'ssh-dss',
37 | 'ecdsa-sha2-nistp256',
38 | 'ecdsa-sha2-nistp384',
39 | 'ecdsa-sha2-nistp521'
40 | ]
41 |
42 | scan_file_type = [
43 | '.pub',
44 | '.pem'
45 | ]
46 |
47 | re_pattern = "-----.+-----"
48 |
49 | path_dir = Config.key_path
50 |
51 | def scan(self):
52 | pathdirs = re.split(":", self.path_dir)
53 | for p in pathdirs:
54 | if not p: continue
55 | for dirpath, dirnames, filenames in os.walk(p):
56 | for filename in filenames:
57 | try:
58 | # key with specific name
59 | if '.' in filename:
60 | for file_type in self.scan_file_type:
61 | if filename.endswith(file_type):
62 | full_path = os.path.join(dirpath, filename)
63 | mode = get_stat(full_path)[1]
64 | content = get_content(full_path)
65 | if content:
66 | _type = self.__get_type(content)
67 | self.add_key(key=content, name=filename, _type=_type, path=full_path, mode=mode)
68 | logging.debug('ScannerKey.scan(): Add key file %s' % filename)
69 | # key without specific name
70 | else:
71 | full_path = os.path.join(dirpath, filename)
72 | mode = get_stat(full_path)[1]
73 | content = get_content(full_path)
74 | if content and re.match(self.re_pattern, content):
75 | _type = self.__get_type(content)
76 | self.add_key(key=content, name=filename, _type=_type, path=full_path, mode=mode)
77 | logging.debug('ScannerKey.scan(): Add key file %s' % filename)
78 | except Exception, e:
79 | #log
80 | logging.error("ScannerKey.scan(): Add file %s failed, %s" % (filename, str(e)))
81 |
82 | def __get_type(self, content):
83 | for _type in self.support_key_type:
84 | if _type in content:
85 | return _type
86 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/user.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-4-4
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | import logging
24 | import pwd
25 | import grp
26 |
27 | from pysa.scanner.actions.base import ScannerBase
28 |
29 |
30 | class ScannerUser(ScannerBase):
31 | def scan(self):
32 | for p in pwd.getpwall():
33 | name, password, uid, gid, gecos, home, shell = p
34 |
35 | groups = []
36 | # get the secondary groups
37 | for gr in grp.getgrall():
38 | gr_name, gr_pwd, gr_gid, gr_mem = gr
39 | # check whether the main group
40 | if gid == gr_gid:
41 | group = gr_name
42 | continue
43 | # check whether the group member
44 | if name in gr_mem:
45 | groups.append(gr_name)
46 |
47 | if group is None or not group: continue
48 |
49 | self.add_user(name=name, uid=uid, gid=gid, group=group, groups=groups, shell=shell, home=home)
50 |
--------------------------------------------------------------------------------
/pysa/scanner/actions/utils.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | import re
24 | import os
25 | import stat
26 | import pwd
27 | import grp
28 | import subprocess
29 | import logging
30 | import string
31 |
32 |
33 | PAT_IP = re.compile(r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$', re.S)
34 | TEXT_CHARACTERS = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
35 | NULL_TRANS = string.maketrans("", "")
36 |
37 | def lsb_release_codename():
38 | """
39 | Return the OS release's codename.
40 | """
41 | if hasattr(lsb_release_codename, '_cache'):
42 | return lsb_release_codename._cache
43 | try:
44 | p = subprocess.Popen(['lsb_release', '-c'], stdout=subprocess.PIPE)
45 | except OSError:
46 | lsb_release_codename._cache = None
47 | return lsb_release_codename._cache
48 | stdout, stderr = p.communicate()
49 | if 0 != p.returncode:
50 | lsb_release_codename._cache = None
51 | return lsb_release_codename._cache
52 | match = re.search(r'\t(\w+)$', stdout)
53 | if match is None:
54 | lsb_release_codename._cache = None
55 | return lsb_release_codename._cache
56 | lsb_release_codename._cache = match.group(1)
57 | return lsb_release_codename._cache
58 |
59 | def rubygems_unversioned():
60 | """
61 | Determine whether RubyGems is suffixed by the Ruby language version.
62 | It ceased to be on Oneiric. It always has been on RPM-based distros.
63 | """
64 | codename = lsb_release_codename()
65 | return codename is None or codename[0] >= 'o'
66 |
67 | def rubygems_virtual():
68 | """
69 | Determine whether RubyGems is baked into the Ruby 1.9 distribution.
70 | It is on Maverick and newer systems.
71 | """
72 | codename = lsb_release_codename()
73 | return codename is not None and codename[0] >= 'm'
74 |
75 | def rubygems_path():
76 | """
77 | Determine based on the OS release where RubyGems will install gems.
78 | """
79 | if lsb_release_codename() is None or rubygems_update():
80 | return '/usr/lib/ruby/gems'
81 | return '/var/lib/gems'
82 |
83 | def mtime(pathname):
84 | try:
85 | return os.stat(pathname).st_mtime
86 | except OSError:
87 | return 0
88 |
89 | # open the cache file
90 | def open_cache(pathname, mode):
91 | f = open(pathname, mode)
92 | uid = int(os.environ['SUDO_UID'])
93 | gid = int(os.environ['SUDO_GID'])
94 | os.fchown(f.fileno(), uid, gid)
95 | return f
96 |
97 | def valid_txtfile(pathname):
98 | # only file
99 | if os.path.isdir(pathname)==True:
100 | return False
101 |
102 | # only plane text file
103 | ###########################################
104 | #cmd = '/usr/bin/file -bi ' + pathname
105 | #f = os.popen(cmd, 'r')
106 | #if f.read().startswith('text') == False:
107 | # return False
108 | ###########################################
109 | if istextfile(pathname)==0:
110 | return False
111 |
112 | # get the ctime;
113 | s = os.lstat(pathname)
114 | # And ignore block special files, character special files,
115 | # pipes, sockets and symbolic links.
116 | if stat.S_ISBLK(s.st_mode) \
117 | or stat.S_ISCHR(s.st_mode) \
118 | or stat.S_ISFIFO(s.st_mode) \
119 | or stat.S_ISSOCK(s.st_mode) \
120 | or stat.S_ISLNK(s.st_mode):
121 | return False
122 |
123 | return True
124 |
125 | def istextfile(filename, blocksize = 512):
126 | try:
127 | ret = istext(open(filename).read(blocksize))
128 | except IOError:
129 | return 0
130 | return ret
131 |
132 | def istext(s):
133 | if "\0" in s:
134 | return 0
135 |
136 | if not s: # Empty files are considered text
137 | return 1
138 |
139 | # Get the non-text characters (maps a character to itself then
140 | # use the 'remove' option to get rid of the text characters.)
141 | t = s.translate(NULL_TRANS, TEXT_CHARACTERS)
142 |
143 | # If more than 30% non-text characters, then
144 | # this is considered a binary file
145 | if len(t)/len(s) > 0.30:
146 | return 0
147 | return 1
148 |
149 | def get_stat(pathname):
150 | try:
151 | s = os.lstat(pathname)
152 | pw = pwd.getpwuid(s.st_uid)
153 | owner = pw.pw_name
154 |
155 | gr = grp.getgrgid(s.st_gid)
156 | group = gr.gr_name
157 |
158 | mode = oct( s.st_mode & 0777 )
159 |
160 | except KeyError:
161 | owner = s.st_uid
162 | group = s.st_gid
163 | mode = oct( 0777 )
164 |
165 | return (group, mode, owner)
166 |
167 | def get_content(pathname):
168 | # read the config file's content
169 | try:
170 | content = open(pathname).read()
171 | return content
172 | except IOError, e:
173 | logging.error('utils.get_content(): Can not get file content, %s' % str(e))
174 | return None
175 |
176 | def get_ips(filename):
177 | ips = []
178 | try:
179 | file = open(filename, "r")
180 |
181 | # read through the file
182 | for line in file.readlines():
183 | line = line.rstrip()
184 |
185 | regex = re.findall(r'[0-9]+(?:\.[0-9]+){3}', line)
186 | # if the regex is not empty and is not already in ips list append
187 | for ip in regex:
188 | if ip is not None and ip not in ips:
189 | if (PAT_IP.match(ip)) and (not ip.startswith('127.')) and (not ip.startswith('0.')):
190 | ips.append(ip)
191 |
192 | file.close()
193 |
194 | except IOError, (errno, strerror):
195 | return ips
196 |
197 | return ips
198 |
--------------------------------------------------------------------------------
/pysa/scanner/object/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisualOps/pysa/a3493826f5feb3ba59bed227fa397ce1c7916e9d/pysa/scanner/object/__init__.py
--------------------------------------------------------------------------------
/pysa/scanner/object/cron.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Cron(ObjectBase):
27 |
28 | def __init__(self, name, command, minute, month, monthday, weekday, hour, target=None, user=None, environment=None):
29 | self.command = command
30 | self.environment = '/bin:/usr/bin:/usr/sbin:' + environment if environment else '/bin:/usr/bin:/usr/sbin'
31 | self.minute = minute
32 | self.month = month
33 | self.monthday = monthday
34 | self.name = name
35 | self.target = target
36 | self.user = user
37 | self.weekday = weekday
38 | self.hour = hour
39 |
--------------------------------------------------------------------------------
/pysa/scanner/object/file.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-27
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class File(ObjectBase):
27 |
28 | def __init__(self, checksum, content, group, mode, owner, path, force=False, provider=None, recurse=None, recurselimit=None, source=None):
29 | self.checksum = checksum
30 | self.content = content
31 | self.group = group
32 | self.mode = mode
33 | self.owner = owner
34 | self.path = path
35 | self.force = force
36 | self.provider = provider
37 | self.recurse = recurse
38 | self.recurselimit = recurselimit
39 | self.source = source
40 |
41 |
--------------------------------------------------------------------------------
/pysa/scanner/object/group.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Group(ObjectBase):
27 | def __init__(self, name, gid, member=None):
28 | self.name = name
29 | self.gid = gid
30 | self.member = None
31 |
--------------------------------------------------------------------------------
/pysa/scanner/object/host.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Host(ObjectBase):
27 |
28 | def __init__(self, ip, name, target, host_aliases=None):
29 | self.ip = ip
30 | self.name = name
31 | self.target = target
32 | self.host_aliases = host_aliases
33 |
--------------------------------------------------------------------------------
/pysa/scanner/object/mount.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Mount(ObjectBase):
27 |
28 | def __init__(self, device, fstype, name, atboot=None, dump=None, remounts=None, options=None, size=None):
29 | self.device = device
30 | self.fstype = fstype
31 | self.name = name
32 | self.atboot = atboot
33 | self.dump = dump
34 | self.remounts = remounts
35 | self.options = options
36 | self.size = size
37 |
--------------------------------------------------------------------------------
/pysa/scanner/object/object_base.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-27
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | import re
24 |
25 |
26 | class ObjectBase(object):
27 |
28 | def prase(self):
29 | format_object = {}
30 | for attr in dir(self):
31 | if type(eval("self.%s" % attr)) in (str, int, dict, list, unicode) and not attr.startswith('_') and attr not in ['primarykey', 'primaryvalue']:
32 | format_object[attr] = eval("self.%s" % attr)
33 | return format_object
34 |
35 | @property
36 | def primarykey(self):
37 | pk = {
38 | 'Package' : 'name',
39 | 'File' : 'path',
40 | 'User' : 'name',
41 | 'Service' : 'name',
42 | 'Repository': 'path',
43 | 'Group' : 'name',
44 | 'Cron' : 'name',
45 | 'Host' : 'name',
46 | 'Mount' : 'device',
47 | 'SSHKey' : 'name',
48 | 'Source' : 'path',
49 | 'Process' : 'pid'
50 | }
51 |
52 | return pk.get(self.__class__.__name__)
53 |
54 | @property
55 | def primaryvalue(self):
56 | return getattr(self, self.primarykey)
57 |
58 | def attr_filter(self, attr_rules):
59 | """
60 | attribute filter
61 | """
62 |
63 | if not attr_rules: return False
64 |
65 | # ignore rule's case
66 | type_list = [ i for i in attr_rules.keys() if i.upper()==(self.__class__.__name__).upper() ]
67 | if not type_list: return False
68 |
69 | the_type = type_list[0]
70 | for (attr, rules) in attr_rules[the_type].items():
71 | if not hasattr(self, attr): continue
72 |
73 | # get the object's attribute value
74 | value = getattr(self, attr)
75 |
76 | # ignore the null attribute value
77 | if value is None or not value: continue
78 |
79 | for rule in rules:
80 | if isinstance(value, list): # list value
81 | if all(isinstance(i, str) for i in value) or all(isinstance(i, unicode) for i in value):
82 | if len([ m.group(0) for i in value for m in [re.match("%s$"%rule, i)] if m ])>0: return True
83 | elif isinstance(value, str) or isinstance(value, unicode): # string
84 | if re.search(rule, value): return True
85 | return False
86 |
87 | def add_filter(self, add_rules):
88 | """
89 | addition filter
90 | """
91 |
92 | if not add_rules: return
93 |
94 | type_list = [ i for i in add_rules.keys() if i.upper()==(self.__class__.__name__).upper() ]
95 | if not type_list: return False
96 |
97 | the_type = type_list[0]
98 | for attr in add_rules[the_type]:
99 | # check whether has the attribute
100 | if not hasattr(self, attr): continue
101 |
102 | values = add_rules[the_type][attr]
103 | # global setting
104 | if isinstance(values, str) or isinstance(values, unicode):
105 | setattr(self, attr, values)
106 | elif isinstance(values, list):
107 | setattr(self, attr, values[0])
108 | # single setting
109 | elif isinstance(values, dict):
110 | for value in values:
111 | if getattr(self, attr) == value: # check whether the object
112 | rules = values[value]
113 | # update
114 | for (add_attr, add_value) in rules.items():
115 | if hasattr(self, add_attr) and len(add_value)>0:
116 | setattr(self, add_attr, add_value[0])
117 |
--------------------------------------------------------------------------------
/pysa/scanner/object/package.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-27
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Package(ObjectBase):
27 |
28 | def __init__(self, name, files=None, description=None, version=None, responsefile=None, provider=None,
29 | instance=None, category=None, platform=None, manager=None, root=None, vendor=None):
30 | self.name = name
31 | self.config_files = files
32 | self.description = description
33 | self.version = version
34 | self.responsefile = responsefile
35 | self.provider = provider
36 | self.instance = instance
37 | self.category = category
38 | self.platform = platform
39 | self.root = root
40 | self.manager = manager
41 | self.vendor = vendor
42 |
--------------------------------------------------------------------------------
/pysa/scanner/object/process.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-19
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Process(ObjectBase):
27 |
28 | def __init__(self, pid, owner, status, cpu, mem, cmd, ppid=None):
29 | self.pid = pid # process id
30 | self.owner = owner
31 | self.status = status # D:uninterruptible sleep, R:runnable, S:sleeping, T:raced or stopped, Z:defunct process
32 | self.cpu = cpu # cpu%
33 | self.mem = mem # mem%
34 | self.cmd = cmd
35 | self.ppid = ppid # parent pid
36 |
--------------------------------------------------------------------------------
/pysa/scanner/object/repository.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-04-18
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Michael
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Repository(ObjectBase):
27 |
28 | def __init__(self, checksum, content, group, mode, owner, path, force=False, provider=None, recurse=None, recurselimit=None, source=None):
29 | self.checksum = checksum
30 | self.content = content
31 | self.group = group
32 | self.mode = mode
33 | self.owner = owner
34 | self.path = path
35 | self.force = force
36 | self.provider = provider
37 | self.recurse = recurse
38 | self.recurselimit = recurselimit
39 | self.source = source
40 |
--------------------------------------------------------------------------------
/pysa/scanner/object/service.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Service(ObjectBase):
27 |
28 | def __init__(self, enable, hasrestart, name, path, \
29 | provider=None, binary=None, control=None, ensure=None, \
30 | hasstatus=None, manifest=None, start=None, stop=None, restart=None):
31 | self.enable = enable
32 | self.hasrestart = hasrestart
33 | self.name = name
34 | self.path = path
35 | self.provider = provider
36 | self.binary = binary
37 | self.control = control,
38 | self.ensure = ensure
39 | self.hasstatus = hasstatus
40 | self.manifest = manifest
41 | self.start = start
42 | self.stop = stop
43 | self.restart = restart
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/pysa/scanner/object/source.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class Source(ObjectBase):
27 |
28 | def __init__(self, source, name, path, owner, group, mode, scm, branch, checksum=None, password=None, key=None):
29 | self.source = source
30 | self.path = path
31 | self.owner = owner
32 | self.name = name
33 | self.group = group
34 | self.mode = mode
35 | self.scm = scm
36 | self.branch = branch
37 | self.checksum = checksum
38 | self.password = password
39 | self.key = key
40 |
--------------------------------------------------------------------------------
/pysa/scanner/object/sshkey.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class SSHKey(ObjectBase):
27 |
28 | def __init__(self, key, name, path, mode, _type=None, target=None, host_aliases=None, user=None):
29 | self.key = key
30 | self.name = name
31 | self.target = target
32 | self.host_aliases = host_aliases
33 | self.type = _type
34 | self.user = user
35 | self.path = path
36 | self.mode = mode
37 |
38 |
--------------------------------------------------------------------------------
/pysa/scanner/object/user.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 |
23 | from pysa.scanner.object.object_base import ObjectBase
24 |
25 |
26 | class User(ObjectBase):
27 |
28 | def __init__(self, name, uid, gid, group, groups=None, password=None, expiry=None, shell=None, home=None):
29 | self.name = name
30 | self.gid = gid
31 | self.group = group
32 | self.expiry = expiry
33 | self.groups = groups
34 | self.password = password
35 | self.uid = uid
36 | self.shell = shell
37 | self.home = home
38 |
--------------------------------------------------------------------------------
/pysa/scanner/scanner_handler.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-3-28
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Ken
21 | '''
22 | #------------------------------------------------------------
23 | import logging
24 | import time
25 |
26 | from pysa.scanner.actions.file import ScannerFile
27 | from pysa.scanner.actions.gem import ScannerGem
28 | from pysa.scanner.actions.npm import ScannerNpm
29 | from pysa.scanner.actions.php import ScannerPhp
30 | from pysa.scanner.actions.pypi import ScannerPypi
31 | from pysa.scanner.actions.service import ScannerService
32 | from pysa.scanner.actions.host import ScannerHost
33 | from pysa.scanner.actions.mount import ScannerMount
34 | from pysa.scanner.actions.cron import ScannerCron
35 | from pysa.scanner.actions.sshkey import ScannerKey
36 | from pysa.scanner.actions.user import ScannerUser
37 | from pysa.scanner.actions.group import ScannerGroup
38 | from pysa.scanner.actions.package import ScannerPackage
39 | from pysa.scanner.actions.source import ScannerSource
40 | from pysa.scanner.actions.repository import ScannerRepo
41 | from pysa.scanner.actions.process import ScannerProcess
42 | from pysa.scanner.actions.base import ScannerBase
43 | #------------------------------------------------------------
44 |
45 | class ScannerHandler():
46 |
47 | # stay aware of the order
48 | handler = {
49 | "file" : ScannerFile,
50 | "gem" : ScannerGem,
51 | "npm" : ScannerNpm,
52 | "php" : ScannerPhp,
53 | "pypi" : ScannerPypi,
54 | "service" : ScannerService,
55 | "host" : ScannerHost,
56 | 'user' : ScannerUser,
57 | 'group' : ScannerGroup,
58 | 'mount' : ScannerMount,
59 | 'cron' : ScannerCron,
60 | 'key' : ScannerKey,
61 | 'package' : ScannerPackage,
62 | 'source' : ScannerSource,
63 | 'repository': ScannerRepo,
64 | 'process' : ScannerProcess
65 | }
66 |
67 |
68 |
69 | def __init__(self, rules):
70 | self.resources = {
71 | 'packages' : {},
72 | 'files' : {},
73 | 'crons' : {},
74 | 'groups' : {},
75 | 'mounts' : {},
76 | 'hosts' : {},
77 | 'repos' : {},
78 | 'services' : {},
79 | 'keys' : {},
80 | 'users' : {},
81 | 'ips' : [],
82 | 'sources' : {},
83 | 'proces' : {}
84 |
85 | }
86 |
87 | self.rules = rules if rules else {}
88 |
89 | def scan(self):
90 |
91 | # init the base scanner
92 | s = ScannerBase(
93 | self.resources['packages'],
94 | self.resources['files'],
95 | self.resources['crons'],
96 | self.resources['groups'],
97 | self.resources['mounts'],
98 | self.resources['hosts'],
99 | self.resources['repos'],
100 | self.resources['services'],
101 | self.resources['keys'],
102 | self.resources['users'],
103 | self.resources['ips'],
104 | self.resources['sources'],
105 | self.resources['proces']
106 | )
107 | # init the filter rules
108 | s.init_filter(self.rules)
109 |
110 | for scanner_key in self.handler.keys():
111 | # ignore the discard resources
112 | if 'discard' in self.rules and '_resources' in self.rules['discard'] and scanner_key in self.rules['discard']['_resources']: continue
113 |
114 | # time begin
115 | time_begin = time.time()
116 |
117 | # log
118 | logging.info('ScannerHandler.scan(): Scanning Module %s, time begin at %s ' % (scanner_key, time_begin))
119 |
120 | # scan according to different modules
121 | try:
122 | s.__class__ = self.handler[scanner_key]
123 |
124 | s.scan()
125 |
126 | except Exception, e:
127 | logging.error('ScannerHandler.scan(): %s Error message, %s' % (scanner_key,str(e)))
128 |
129 | # time end
130 | time_consume = time.time() - time_begin
131 | logging.info('ScannerHandler.scan(): Scanning Module %s, time consume %s ' % (scanner_key, time_consume))
132 |
133 | return self.resources
134 |
135 | def module_scan(filters = None):
136 | scanner = ScannerHandler(filters)
137 | return scanner.scan()
138 |
--------------------------------------------------------------------------------
/pysa/tools.py:
--------------------------------------------------------------------------------
1 | '''
2 | Common tools
3 |
4 | pysa - reverse a complete computer setup
5 | Copyright (C) 2013 MadeiraCloud Ltd.
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 |
17 | You should have received a copy of the GNU General Public License
18 | along with this program. If not, see .
19 |
20 | @author: Thibault BRONCHAIN
21 | '''
22 |
23 | import os
24 | import os.path
25 | import logging
26 | import re
27 | import copy
28 |
29 | from pysa.exception import *
30 |
31 |
32 | INFO = "INFO"
33 | DEBUG = "DEBUG"
34 | ERR = "ERROR"
35 |
36 | LOGGING_EQ = {
37 | INFO : logging.info,
38 | DEBUG : logging.debug,
39 | ERR : logging.error
40 | }
41 |
42 |
43 | # common tools collection
44 | class Tools():
45 | # logging
46 | @staticmethod
47 | def l(action, content, f, c = None):
48 | out = ("%s." % (c.__class__.__name__) if c else "")
49 | out += "%s()" % (f)
50 | LOGGING_EQ[action]("%s: %s" % (out, content))
51 |
52 | # add a tab
53 | @staticmethod
54 | def tab_inc(tab):
55 | return tab+'\t'
56 |
57 | # delete a tab
58 | @staticmethod
59 | def tab_dec(tab):
60 | return tab[1:]
61 |
62 | # write data in a specific file
63 | @staticmethod
64 | def write_in_file(fname, content):
65 | Tools.l(INFO, "creating file %s" % (fname), 'write_in_file')
66 | dirname = os.path.dirname(fname)
67 | if not os.path.exists(dirname):
68 | os.makedirs(dirname)
69 | f = open(fname,'w')
70 | f.write(content)
71 | f.close()
72 |
73 | # get recursive path dirlist
74 | @staticmethod
75 | def get_recurse_path(path):
76 | rpath = re.split('/', path)
77 | i = 1
78 | while i < len(rpath):
79 | #DEBUG
80 | # rpath[i] = "%s/%s" % (rpath[i-1] if i else "", rpath[i])
81 | #/DEBUG
82 | rpath[i] = os.path.normpath("%s/%s" % (rpath[i-1] if i else "", rpath[i]))
83 | i += 1
84 | rpath[0] = '/'
85 | return rpath
86 |
87 | # get previous path
88 | @staticmethod
89 | def path_basename(path):
90 | if path == '/': return None
91 | if path[-1] == '/': path = path[:-1]
92 | rpath = re.split('/', path)
93 | i = 1
94 | while i < len(rpath):
95 | # DEBUG
96 | # rpath[i] = "%s/%s" % (rpath[i-1] if i else "", rpath[i])
97 | #/DEBUG
98 | rpath[i] = os.path.normpath("%s/%s" % (rpath[i-1] if i else "", rpath[i]))
99 | i += 1
100 | rpath[0] = '/'
101 | if len(rpath) < 2: return None
102 | return rpath[-2]
103 |
104 | # returns file content
105 | @staticmethod
106 | def get_file(filename):
107 | if not filename: return None
108 | file = None
109 | try:
110 | f = open(filename, 'r')
111 | file = f.read()
112 | except IOError:
113 | Tools.l(ERR, "%s: no such file or directory" % (filename), 'dump_file')
114 | return None
115 | return file
116 |
117 | # check if file exists
118 | @staticmethod
119 | def file_exists(filename):
120 | if not filename: return None
121 | try:
122 | with open(filename): pass
123 | except IOError:
124 | Tools.l(ERR, "%s: no such file or directory" % (filename), 'file_exists')
125 | return None
126 | return filename
127 |
128 | # merge lists recursive
129 | @staticmethod
130 | def list_merging(first, second):
131 | f = (first if first else [])
132 | s = (second if second else [])
133 | return f+s
134 |
135 | # remove childs after dict merging
136 | @staticmethod
137 | def dict_cleaner(input):
138 | output = {}
139 | for key in input:
140 | if type(input[key]) is not dict:
141 | output[key] = input[key]
142 | return output
143 |
144 | # ensure dictionary existency
145 | @staticmethod
146 | def s_dict_merging(first, second, duplicate = True):
147 | d = Tools.dict_merging(first,second, duplicate)
148 | return (d if d else {})
149 |
150 | # merge dicts /!\ recursive
151 | @staticmethod
152 | def dict_merging(first, second, duplicate = True):
153 | if (not first) and (not second):
154 | return None
155 | elif not first:
156 | return (copy.deepcopy(second) if duplicate else second)
157 | elif not second:
158 | return (copy.deepcopy(first) if duplicate else first)
159 | repl = copy.deepcopy(first)
160 | for item in second:
161 | if (first.get(item)) and (type(first[item]) != type (second[item])):
162 | continue
163 | elif not first.get(item):
164 | repl[item] = second[item]
165 | elif type(second[item]) is dict:
166 | # recursion here
167 | val = Tools.dict_merging(first[item], second.get(item), True)
168 | if val != None:
169 | repl[item] = val
170 | elif type(second[item]) is list:
171 | repl[item] = first[item] + second[item]
172 | else:
173 | repl[item] = second[item]
174 | return repl
175 |
176 | # merge strings and lists
177 | @staticmethod
178 | def merge_string_list(first, second):
179 | if not first: return second
180 | elif not second: return first
181 | elif (type(first) is list) and (type(second) is list): return first+second
182 | elif (type(first) is list):
183 | first.append(second)
184 | return first
185 | elif (type(second) is list):
186 | second.append(first)
187 | return second
188 | else: return [first, second]
189 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | '''
3 | Installation script for setup from pip
4 |
5 | pysa - reverse a complete computer setup
6 | Copyright (C) 2013 MadeiraCloud Ltd.
7 |
8 | This program is free software: you can redistribute it and/or modify
9 | it under the terms of the GNU General Public License as published by
10 | the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see .
20 |
21 | @author: Thibault BRONCHAIN
22 | '''
23 |
24 | import os
25 | import sys
26 | import shutil
27 | #import subprocess
28 | import setuptools
29 |
30 | from distutils.core import setup
31 | #from distutils.command.install import install
32 | #from distutils.command.sdist import sdist
33 |
34 | DISTNAME = 'Pysa'
35 | VERSION = '0.3b5'
36 | DESCRIPTION = 'Reverse your Servers Configuration'
37 | LONG_DESCRIPTION = open('README.txt').read()
38 | MAINTAINER = 'Thibault BRONCHAIN - MadeiraCloud Ltd.'
39 | MAINTAINER_EMAIL = 'pysa@mc2.io'
40 | LICENSE = 'LICENSE.txt'
41 | URL = 'http://madeiracloud.github.io/pysa/'
42 | DOWNLOAD_URL = 'http://pypi.python.org/packages/source/P/Pysa/Pysa-'+VERSION+'.tar.gz'
43 | #PACKAGES = ['pysa']
44 | #PACKAGE_DIR = {'pysa': 'pysa'}
45 | SCRIPTS = ['bin/pysa2puppet', 'bin/pysa']
46 |
47 | #def abspath(path):
48 | # return os.path.abspath(os.path.join(os.path.dirname(__file__), path))
49 | #
50 | #class pysa_install(install):
51 | # user_options = install.user_options
52 | #
53 | # def initialize_options(self):
54 | # install.initialize_options(self)
55 | #
56 | # def run(self):
57 | # install.run(self)
58 | #
59 | # man_dir = abspath("./docs/man/")
60 | # output = subprocess.Popen(
61 | # [os.path.join(man_dir, "pysa_man.sh")],
62 | # stdout=subprocess.PIPE,
63 | # stderr=subprocess.PIPE,
64 | # cwd=man_dir,
65 | # env=dict({"PREFIX": self.prefix}, **dict(os.environ))
66 | # ).communicate()[0]
67 | # print output
68 | #
69 | #class pysa_sdist(sdist):
70 | # def run(self):
71 | # sdist.run(self)
72 |
73 | if __name__ == "__main__":
74 | pkg = setuptools.find_packages()
75 | setup(name=DISTNAME,
76 | version=VERSION,
77 | author=MAINTAINER,
78 | author_email=MAINTAINER_EMAIL,
79 | maintainer=MAINTAINER,
80 | maintainer_email=MAINTAINER_EMAIL,
81 | url=URL,
82 | description=DESCRIPTION,
83 | long_description=LONG_DESCRIPTION,
84 | download_url=DOWNLOAD_URL,
85 | license=LICENSE,
86 | packages = pkg,
87 | # packages=PACKAGES,
88 | # package_dir=PACKAGE_DIR,
89 | scripts=SCRIPTS,
90 | classifiers = [
91 | 'Development Status :: 4 - Beta',
92 | 'Environment :: Console',
93 | 'Intended Audience :: System Administrators',
94 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
95 | 'Operating System :: POSIX :: Linux',
96 | 'Topic :: System',
97 | ],
98 | # cmdclass={"install": pysa_install, "sdist": pysa_sdist},
99 | )
100 |
--------------------------------------------------------------------------------