mirror of https://github.com/veops/cmdb.git
升级后端并开源UI
This commit is contained in:
parent
02c01f60bf
commit
20fd6393e4
|
@ -0,0 +1,7 @@
|
|||
# Environment variable overrides for local development
|
||||
FLASK_APP=autoapp.py
|
||||
FLASK_DEBUG=1
|
||||
FLASK_ENV=development
|
||||
GUNICORN_WORKERS=1
|
||||
LOG_LEVEL=debug
|
||||
SECRET_KEY='YourSecretKey'
|
|
@ -1,21 +1,95 @@
|
|||
*~
|
||||
*.pyc
|
||||
.idea
|
||||
data
|
||||
logs/*
|
||||
.vscode
|
||||
migrates
|
||||
*/logs/*
|
||||
config.cfg
|
||||
*.sql
|
||||
test/*
|
||||
tools/*
|
||||
cmdb_agent/*
|
||||
logs/*
|
||||
*.log
|
||||
*_packed.js
|
||||
*_packed.css
|
||||
*.orig
|
||||
*.zip
|
||||
*.swp
|
||||
config.cfg
|
||||
*.tar.gz
|
||||
core/special.py
|
||||
lib/special
|
||||
lib/audit*
|
||||
templates/*audit*
|
||||
codeLin*
|
||||
lib/spec_*
|
||||
nohup.out
|
||||
.DS_Store
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
#lib
|
||||
#lib64
|
||||
Pipfile.lock
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
.pytest_cache
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
docs/_build
|
||||
|
||||
# Virtualenvs
|
||||
env/
|
||||
|
||||
|
||||
# Configuration
|
||||
api/settings.py
|
||||
tests/settings.py
|
||||
|
||||
# Development database
|
||||
*.db
|
||||
|
||||
# UI
|
||||
ui/.DS_Store
|
||||
ui/node_modules
|
||||
ui/dist
|
||||
|
||||
# local env files
|
||||
ui/.env.local
|
||||
ui/.env.*.local
|
||||
|
||||
# Log files
|
||||
ui/npm-debug.log*
|
||||
ui/yarn-debug.log*
|
||||
ui/yarn-error.log*
|
||||
ui/yarn.lock
|
||||
|
||||
# Editor directories and files
|
||||
ui/.idea
|
||||
ui/.vscode
|
||||
ui/*.suo
|
||||
ui/*.ntvs*
|
||||
ui/*.njsproj
|
||||
ui/*.sln
|
||||
ui/*.sw*
|
||||
|
||||
|
||||
|
|
353
LICENSE
353
LICENSE
|
@ -1,340 +1,21 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
MIT License
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
Copyright (c) pycook
|
||||
|
||||
Preamble
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
.PHONY: docs test
|
||||
|
||||
help:
|
||||
@echo " env create a development environment using virtualenv"
|
||||
@echo " deps install dependencies using pip"
|
||||
@echo " clean remove unwanted files like .pyc's"
|
||||
@echo " lint check style with flake8"
|
||||
@echo " test run all your tests using py.test"
|
||||
|
||||
env:
|
||||
sudo easy_install pip && \
|
||||
pip install pipenv &&
|
||||
make deps
|
||||
|
||||
deps:
|
||||
pipenv install --dev
|
||||
|
||||
clean:
|
||||
python manage.py clean
|
||||
|
||||
lint:
|
||||
flake8 --exclude=env .
|
||||
|
||||
test:
|
||||
py.test tests
|
|
@ -0,0 +1,59 @@
|
|||
[[source]]
|
||||
url = "https://mirrors.aliyun.com/pypi/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
# Flask
|
||||
Flask = "==1.0.3"
|
||||
Werkzeug = "==0.15.4"
|
||||
click = ">=5.0"
|
||||
# Api
|
||||
Flask-RESTful = "==0.3.7"
|
||||
# Database
|
||||
Flask-SQLAlchemy = "==2.4.0"
|
||||
SQLAlchemy = "==1.3.5"
|
||||
PyMySQL = "==0.9.3"
|
||||
redis = "==3.2.1"
|
||||
# Migrations
|
||||
Flask-Migrate = "==2.5.2"
|
||||
# Deployment
|
||||
gevent = "==1.4.0"
|
||||
gunicorn = ">=19.1.1"
|
||||
supervisor = "==4.0.3"
|
||||
# Auth
|
||||
Flask-Login = "==0.4.1"
|
||||
Flask-Bcrypt = "==0.7.1"
|
||||
Flask-Cors = ">=3.0.8"
|
||||
# Caching
|
||||
Flask-Caching = ">=1.0.0"
|
||||
# Environment variable parsing
|
||||
environs = "==4.2.0"
|
||||
marshmallow = "==2.20.2"
|
||||
# async tasks
|
||||
celery = "==4.3.0"
|
||||
more-itertools = "==5.0.0"
|
||||
kombu = "==4.4.0"
|
||||
# other
|
||||
six = "==1.12.0"
|
||||
bs4 = ">=0.0.1"
|
||||
toposort = ">=1.5"
|
||||
requests = ">=2.22.0"
|
||||
PyJWT = ">=1.7.1"
|
||||
|
||||
[dev-packages]
|
||||
# Testing
|
||||
pytest = "==4.6.5"
|
||||
WebTest = "==2.0.33"
|
||||
factory-boy = "==2.12.*"
|
||||
pdbpp = "==0.10.0"
|
||||
# Lint and code style
|
||||
flake8 = "==3.7.7"
|
||||
flake8-blind-except = "==0.1.1"
|
||||
flake8-debugger = "==3.1.0"
|
||||
flake8-docstrings = "==1.3.0"
|
||||
flake8-isort = "==2.7.0"
|
||||
isort = "==4.3.21"
|
||||
pep8-naming = "==0.8.2"
|
||||
pydocstyle = "==3.0.0"
|
||||
|
56
README.md
56
README.md
|
@ -1,4 +1,54 @@
|
|||
## cmdb
|
||||
<h1 align="center">CMDB</h1>
|
||||
<div align="center">
|
||||
尽可能实现比较通用的运维资产数据的配置和管理
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/pycook/cmdb/blob/master/LICENSE)
|
||||
[](https://github.com/sendya/ant-design-pro-vue)
|
||||
[](https://github.com/pallets/flask)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
- 在线预览: [CMDB](url "http://39.100.252.148:8000")
|
||||
- username: admin
|
||||
- password: admin
|
||||
|
||||
Overview
|
||||
----
|
||||

|
||||
|
||||

|
||||
|
||||
环境和依赖
|
||||
----
|
||||
- 存储: mysql, redis
|
||||
- python版本: python2.7, >=python3.6
|
||||
|
||||
|
||||
安装
|
||||
----
|
||||
- 创建数据库cmdb
|
||||
|
||||
- 拉取代码
|
||||
```bash
|
||||
git clone https://github.com/pycook/cmdb.git
|
||||
cd cmdb
|
||||
cp api/settings.py.example api/settings.py
|
||||
```
|
||||
设置api/settings.py里的database
|
||||
|
||||
- 安装库
|
||||
- 后端: ```pipenv run pipenv install```
|
||||
- 前端: ```cd ui && yarn install && cd ..```
|
||||
|
||||
- 创建数据库表 ```flask run flask db-setup```
|
||||
|
||||
- 启动服务
|
||||
- 后端: ```pipenv run flask run```
|
||||
- 前端: ```cd ui && yarn run serve```
|
||||
- 浏览器打开: [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
|
||||
### cmdb即配置管理数据库
|
||||
### 该部分为API,Portal即将单独开源
|
120
__init__.py
120
__init__.py
|
@ -1,120 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import SMTPHandler
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import g
|
||||
from flask.ext.babel import Babel
|
||||
from flask.ext.principal import identity_loaded
|
||||
from flask.ext.principal import Principal
|
||||
|
||||
import core
|
||||
from extensions import db
|
||||
from extensions import mail
|
||||
from extensions import cache
|
||||
from extensions import celery
|
||||
from extensions import rd
|
||||
from models.account import User
|
||||
from lib.template import filters
|
||||
|
||||
|
||||
APP_NAME = "CMDB-API"
|
||||
|
||||
MODULES = (
|
||||
(core.attribute, "/api/v0.1/attributes"),
|
||||
(core.citype, "/api/v0.1/citypes"),
|
||||
(core.cityperelation, "/api/v0.1/cityperelations"),
|
||||
(core.cirelation, "/api/v0.1/cirelations"),
|
||||
(core.ci, "/api/v0.1/ci"),
|
||||
(core.history, "/api/v0.1/history"),
|
||||
(core.account, "/api/v0.1/accounts"),
|
||||
(core.special, ""),
|
||||
)
|
||||
|
||||
|
||||
def make_app(config=None, modules=None):
|
||||
if not modules:
|
||||
modules = MODULES
|
||||
app = Flask(APP_NAME)
|
||||
app.config.from_pyfile(config)
|
||||
configure_extensions(app)
|
||||
configure_i18n(app)
|
||||
configure_identity(app)
|
||||
configure_blueprints(app, modules)
|
||||
configure_logging(app)
|
||||
configure_template_filters(app)
|
||||
return app
|
||||
|
||||
|
||||
def configure_extensions(app):
|
||||
db.app = app
|
||||
db.init_app(app)
|
||||
mail.init_app(app)
|
||||
cache.init_app(app)
|
||||
celery.init_app(app)
|
||||
rd.init_app(app)
|
||||
|
||||
|
||||
def configure_i18n(app):
|
||||
babel = Babel(app)
|
||||
|
||||
@babel.localeselector
|
||||
def get_locale():
|
||||
accept_languages = app.config.get('ACCEPT_LANGUAGES', ['en', 'zh'])
|
||||
return request.accept_languages.best_match(accept_languages)
|
||||
|
||||
|
||||
def configure_modules(app, modules):
|
||||
for module, url_prefix in modules:
|
||||
app.register_module(module, url_prefix=url_prefix)
|
||||
|
||||
|
||||
def configure_blueprints(app, modules):
|
||||
for module, url_prefix in modules:
|
||||
app.register_blueprint(module, url_prefix=url_prefix)
|
||||
|
||||
|
||||
def configure_identity(app):
|
||||
principal = Principal(app)
|
||||
@identity_loaded.connect_via(app)
|
||||
def on_identity_loaded(sender, identity):
|
||||
g.user = User.query.from_identity(identity)
|
||||
|
||||
|
||||
def configure_logging(app):
|
||||
hostname = os.uname()[1]
|
||||
mail_handler = SMTPHandler(
|
||||
app.config['MAIL_SERVER'],
|
||||
app.config['DEFAULT_MAIL_SENDER'],
|
||||
app.config['ADMINS'],
|
||||
'[%s] CMDB API error' % hostname,
|
||||
(
|
||||
app.config['MAIL_USERNAME'],
|
||||
app.config['MAIL_PASSWORD'],
|
||||
)
|
||||
)
|
||||
mail_formater = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s %(pathname)s %(lineno)d\n%(message)s")
|
||||
mail_handler.setFormatter(mail_formater)
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
if not app.debug:
|
||||
app.logger.addHandler(mail_handler)
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s %(pathname)s %(lineno)d - %(message)s")
|
||||
log_file = app.config['LOG_PATH']
|
||||
file_handler = TimedRotatingFileHandler(
|
||||
log_file, when='d', interval=1, backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
|
||||
|
||||
def configure_template_filters(app):
|
||||
for name in dir(filters):
|
||||
if callable(getattr(filters, name)):
|
||||
app.add_template_filter(getattr(filters, name))
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -0,0 +1,166 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""The app module, containing the app factory function."""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from inspect import getmembers
|
||||
|
||||
from flask import Flask
|
||||
from flask import make_response, jsonify
|
||||
from flask.blueprints import Blueprint
|
||||
from flask.cli import click
|
||||
|
||||
import api.views
|
||||
from api.models.account import User
|
||||
from api.flask_cas import CAS
|
||||
from api.extensions import (
|
||||
bcrypt,
|
||||
cors,
|
||||
cache,
|
||||
db,
|
||||
login_manager,
|
||||
migrate,
|
||||
celery,
|
||||
rd,
|
||||
)
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
PROJECT_ROOT = os.path.join(HERE, os.pardir)
|
||||
API_PACKAGE = "api"
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
"""Load user by ID."""
|
||||
return User.get_by_id(int(user_id))
|
||||
|
||||
|
||||
class ReverseProxy(object):
|
||||
"""Wrap the application in this middleware and configure the
|
||||
front-end server to add these headers, to let you quietly bind
|
||||
this to a URL other than / and to an HTTP scheme that is
|
||||
different than what is used locally.
|
||||
|
||||
In nginx:
|
||||
location /myprefix {
|
||||
proxy_pass http://192.168.0.1:5001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Script-Name /myprefix;
|
||||
}
|
||||
|
||||
:param app: the WSGI application
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||
if script_name:
|
||||
environ['SCRIPT_NAME'] = script_name
|
||||
path_info = environ['PATH_INFO']
|
||||
if path_info.startswith(script_name):
|
||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||
|
||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
||||
if scheme:
|
||||
environ['wsgi.url_scheme'] = scheme
|
||||
return self.app(environ, start_response)
|
||||
|
||||
|
||||
def create_app(config_object="{0}.settings".format(API_PACKAGE)):
|
||||
"""Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/.
|
||||
|
||||
:param config_object: The configuration object to use.
|
||||
"""
|
||||
app = Flask(__name__.split(".")[0])
|
||||
app.config.from_object(config_object)
|
||||
register_extensions(app)
|
||||
register_blueprints(app)
|
||||
register_error_handlers(app)
|
||||
register_shell_context(app)
|
||||
register_commands(app)
|
||||
configure_logger(app)
|
||||
CAS(app)
|
||||
app.wsgi_app = ReverseProxy(app.wsgi_app)
|
||||
return app
|
||||
|
||||
|
||||
def register_extensions(app):
|
||||
"""Register Flask extensions."""
|
||||
bcrypt.init_app(app)
|
||||
cache.init_app(app)
|
||||
db.init_app(app)
|
||||
cors.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
rd.init_app(app)
|
||||
celery.conf.update(app.config)
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
for item in getmembers(api.views):
|
||||
if item[0].startswith("blueprint") and isinstance(item[1], Blueprint):
|
||||
app.register_blueprint(item[1])
|
||||
|
||||
|
||||
def register_error_handlers(app):
|
||||
"""Register error handlers."""
|
||||
|
||||
def render_error(error):
|
||||
"""Render error template."""
|
||||
import traceback
|
||||
app.logger.error(traceback.format_exc())
|
||||
error_code = getattr(error, "code", 500)
|
||||
return make_response(jsonify(message=str(error)), error_code)
|
||||
|
||||
for errcode in app.config.get("ERROR_CODES") or [400, 401, 403, 404, 405, 500, 502]:
|
||||
app.errorhandler(errcode)(render_error)
|
||||
app.handle_exception = render_error
|
||||
|
||||
|
||||
def register_shell_context(app):
|
||||
"""Register shell context objects."""
|
||||
|
||||
def shell_context():
|
||||
"""Shell context objects."""
|
||||
return {"db": db, "User": User}
|
||||
|
||||
app.shell_context_processor(shell_context)
|
||||
|
||||
|
||||
def register_commands(app):
|
||||
"""Register Click commands."""
|
||||
for root, _, files in os.walk(os.path.join(HERE, "commands")):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("commands"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
command = __import__(os.path.splitext(filename)[0])
|
||||
func_list = [o[0] for o in getmembers(command) if isinstance(o[1], click.core.Command)]
|
||||
for func_name in func_list:
|
||||
app.cli.add_command(getattr(command, func_name))
|
||||
|
||||
|
||||
def configure_logger(app):
|
||||
"""Configure loggers."""
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s %(levelname)s %(pathname)s %(lineno)d - %(message)s")
|
||||
|
||||
if app.debug:
|
||||
handler.setFormatter(formatter)
|
||||
app.logger.addHandler(handler)
|
||||
|
||||
log_file = app.config['LOG_PATH']
|
||||
file_handler = RotatingFileHandler(log_file,
|
||||
maxBytes=2 ** 30,
|
||||
backupCount=7)
|
||||
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
||||
file_handler.setFormatter(formatter)
|
||||
app.logger.addHandler(file_handler)
|
||||
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))
|
|
@ -1,4 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
__all__ = []
|
|
@ -0,0 +1,152 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Click commands."""
|
||||
import os
|
||||
from glob import glob
|
||||
from subprocess import call
|
||||
|
||||
import click
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
||||
|
||||
from api.extensions import db
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
PROJECT_ROOT = os.path.join(HERE, os.pardir, os.pardir)
|
||||
TEST_PATH = os.path.join(PROJECT_ROOT, "tests")
|
||||
|
||||
|
||||
@click.command()
|
||||
def test():
|
||||
"""Run the tests."""
|
||||
import pytest
|
||||
|
||||
rv = pytest.main([TEST_PATH, "--verbose"])
|
||||
exit(rv)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"-f",
|
||||
"--fix-imports",
|
||||
default=True,
|
||||
is_flag=True,
|
||||
help="Fix imports using isort, before linting",
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--check",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Don't make any changes to files, just confirm they are formatted correctly",
|
||||
)
|
||||
def lint(fix_imports, check):
|
||||
"""Lint and check code style with black, flake8 and isort."""
|
||||
skip = ["node_modules", "requirements", "migrations"]
|
||||
root_files = glob("*.py")
|
||||
root_directories = [
|
||||
name for name in next(os.walk("."))[1] if not name.startswith(".")
|
||||
]
|
||||
files_and_directories = [
|
||||
arg for arg in root_files + root_directories if arg not in skip
|
||||
]
|
||||
|
||||
def execute_tool(description, *args):
|
||||
"""Execute a checking tool with its arguments."""
|
||||
command_line = list(args) + files_and_directories
|
||||
click.echo("{}: {}".format(description, " ".join(command_line)))
|
||||
rv = call(command_line)
|
||||
if rv != 0:
|
||||
exit(rv)
|
||||
|
||||
isort_args = ["-rc"]
|
||||
black_args = []
|
||||
if check:
|
||||
isort_args.append("-c")
|
||||
black_args.append("--check")
|
||||
if fix_imports:
|
||||
execute_tool("Fixing import order", "isort", *isort_args)
|
||||
execute_tool("Formatting style", "black", *black_args)
|
||||
execute_tool("Checking code style", "flake8")
|
||||
|
||||
|
||||
@click.command()
|
||||
def clean():
|
||||
"""Remove *.pyc and *.pyo files recursively starting at current directory.
|
||||
|
||||
Borrowed from Flask-Script, converted to use Click.
|
||||
"""
|
||||
for dirpath, dirnames, filenames in os.walk("."):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".pyc") or filename.endswith(".pyo"):
|
||||
full_pathname = os.path.join(dirpath, filename)
|
||||
click.echo("Removing {}".format(full_pathname))
|
||||
os.remove(full_pathname)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--url", default=None, help="Url to test (ex. /static/image.png)")
|
||||
@click.option(
|
||||
"--order", default="rule", help="Property on Rule to order by (default: rule)"
|
||||
)
|
||||
@with_appcontext
|
||||
def urls(url, order):
|
||||
"""Display all of the url matching routes for the project.
|
||||
|
||||
Borrowed from Flask-Script, converted to use Click.
|
||||
"""
|
||||
rows = []
|
||||
column_headers = ("Rule", "Endpoint", "Arguments")
|
||||
|
||||
if url:
|
||||
try:
|
||||
rule, arguments = current_app.url_map.bind("localhost").match(
|
||||
url, return_rule=True
|
||||
)
|
||||
rows.append((rule.rule, rule.endpoint, arguments))
|
||||
column_length = 3
|
||||
except (NotFound, MethodNotAllowed) as e:
|
||||
rows.append(("<{}>".format(e), None, None))
|
||||
column_length = 1
|
||||
else:
|
||||
rules = sorted(
|
||||
current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order)
|
||||
)
|
||||
for rule in rules:
|
||||
rows.append((rule.rule, rule.endpoint, None))
|
||||
column_length = 2
|
||||
|
||||
str_template = ""
|
||||
table_width = 0
|
||||
|
||||
if column_length >= 1:
|
||||
max_rule_length = max(len(r[0]) for r in rows)
|
||||
max_rule_length = max_rule_length if max_rule_length > 4 else 4
|
||||
str_template += "{:" + str(max_rule_length) + "}"
|
||||
table_width += max_rule_length
|
||||
|
||||
if column_length >= 2:
|
||||
max_endpoint_length = max(len(str(r[1])) for r in rows)
|
||||
max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8
|
||||
str_template += " {:" + str(max_endpoint_length) + "}"
|
||||
table_width += 2 + max_endpoint_length
|
||||
|
||||
if column_length >= 3:
|
||||
max_arguments_length = max(len(str(r[2])) for r in rows)
|
||||
max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9
|
||||
str_template += " {:" + str(max_arguments_length) + "}"
|
||||
table_width += 2 + max_arguments_length
|
||||
|
||||
click.echo(str_template.format(*column_headers[:column_length]))
|
||||
click.echo("-" * table_width)
|
||||
|
||||
for row in rows:
|
||||
click.echo(str_template.format(*row[:column_length]))
|
||||
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def db_setup():
|
||||
"""create tables
|
||||
"""
|
||||
db.create_all()
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_caching import Cache
|
||||
from flask_login import LoginManager
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_cors import CORS
|
||||
from celery import Celery
|
||||
|
||||
from api.lib.utils import RedisHandler
|
||||
|
||||
bcrypt = Bcrypt()
|
||||
login_manager = LoginManager()
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
cache = Cache()
|
||||
celery = Celery()
|
||||
cors = CORS(supports_credentials=True)
|
||||
rd = RedisHandler(prefix="CMDB_CI") # TODO
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
"""
|
||||
flask_cas.__init__
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import current_app
|
||||
|
||||
# Find the stack on which we want to store the database connection.
|
||||
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
|
||||
# before that we need to use the _request_ctx_stack.
|
||||
try:
|
||||
from flask import _app_ctx_stack as stack
|
||||
except ImportError:
|
||||
from flask import _request_ctx_stack as stack
|
||||
|
||||
from api.flask_cas import routing
|
||||
|
||||
|
||||
class CAS(object):
|
||||
"""
|
||||
Required Configs:
|
||||
|
||||
|Key |
|
||||
|----------------|
|
||||
|CAS_SERVER |
|
||||
|CAS_AFTER_LOGIN |
|
||||
|
||||
Optional Configs:
|
||||
|
||||
|Key | Default |
|
||||
|-------------------------|----------------|
|
||||
|CAS_TOKEN_SESSION_KEY | _CAS_TOKEN |
|
||||
|CAS_USERNAME_SESSION_KEY | CAS_USERNAME |
|
||||
|CAS_LOGIN_ROUTE | '/cas' |
|
||||
|CAS_LOGOUT_ROUTE | '/cas/logout' |
|
||||
|CAS_VALIDATE_ROUTE | '/cas/validate'|
|
||||
"""
|
||||
|
||||
def __init__(self, app=None, url_prefix=None):
|
||||
self._app = app
|
||||
if app is not None:
|
||||
self.init_app(app, url_prefix)
|
||||
|
||||
def init_app(self, app, url_prefix=None):
|
||||
# Configuration defaults
|
||||
app.config.setdefault('CAS_TOKEN_SESSION_KEY', '_CAS_TOKEN')
|
||||
app.config.setdefault('CAS_USERNAME_SESSION_KEY', 'CAS_USERNAME')
|
||||
app.config.setdefault('CAS_LOGIN_ROUTE', '/login')
|
||||
app.config.setdefault('CAS_LOGOUT_ROUTE', '/logout')
|
||||
app.config.setdefault('CAS_VALIDATE_ROUTE', '/serviceValidate')
|
||||
# Register Blueprint
|
||||
app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
|
||||
|
||||
# Use the newstyle teardown_appcontext if it's available,
|
||||
# otherwise fall back to the request context
|
||||
if hasattr(app, 'teardown_appcontext'):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
else:
|
||||
app.teardown_request(self.teardown)
|
||||
|
||||
def teardown(self, exception):
|
||||
ctx = stack.top
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return self._app or current_app
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return flask.session.get(
|
||||
self.app.config['CAS_USERNAME_SESSION_KEY'], None)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
return flask.session.get(
|
||||
self.app.config['CAS_TOKEN_SESSION_KEY'], None)
|
|
@ -0,0 +1,122 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
"""
|
||||
flask_cas.cas_urls
|
||||
|
||||
Functions for creating urls to access CAS.
|
||||
"""
|
||||
from six.moves.urllib.parse import quote
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from six.moves.urllib.parse import urljoin
|
||||
|
||||
|
||||
def create_url(base, path=None, *query):
|
||||
""" Create a url.
|
||||
|
||||
Creates a url by combining base, path, and the query's list of
|
||||
key/value pairs. Escaping is handled automatically. Any
|
||||
key/value pair with a value that is None is ignored.
|
||||
|
||||
Keyword arguments:
|
||||
base -- The left most part of the url (ex. http://localhost:5000).
|
||||
path -- The path after the base (ex. /foo/bar).
|
||||
query -- A list of key value pairs (ex. [('key', 'value')]).
|
||||
|
||||
Example usage:
|
||||
>>> create_url(
|
||||
... 'http://localhost:5000',
|
||||
... 'foo/bar',
|
||||
... ('key1', 'value'),
|
||||
... ('key2', None), # Will not include None
|
||||
... ('url', 'http://example.com'),
|
||||
... )
|
||||
'http://localhost:5000/foo/bar?key1=value&url=http%3A%2F%2Fexample.com'
|
||||
"""
|
||||
url = base
|
||||
# Add the path to the url if it's not None.
|
||||
if path is not None:
|
||||
url = urljoin(url, quote(path))
|
||||
# Remove key/value pairs with None values.
|
||||
query = filter(lambda pair: pair[1] is not None, query)
|
||||
# Add the query string to the url
|
||||
url = urljoin(url, '?{0}'.format(urlencode(list(query))))
|
||||
return url
|
||||
|
||||
|
||||
def create_cas_login_url(cas_url, cas_route, service,
|
||||
renew=None, gateway=None):
|
||||
""" Create a CAS login URL .
|
||||
|
||||
Keyword arguments:
|
||||
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
|
||||
cas_route -- The route where the CAS lives on server (ex. /cas)
|
||||
service -- (ex. http://localhost:5000/login)
|
||||
renew -- "true" or "false"
|
||||
gateway -- "true" or "false"
|
||||
|
||||
Example usage:
|
||||
>>> create_cas_login_url(
|
||||
... 'http://sso.pdx.edu',
|
||||
... '/cas',
|
||||
... 'http://localhost:5000',
|
||||
... )
|
||||
'http://sso.pdx.edu/cas?service=http%3A%2F%2Flocalhost%3A5000'
|
||||
"""
|
||||
return create_url(
|
||||
cas_url,
|
||||
cas_route,
|
||||
('service', service),
|
||||
('renew', renew),
|
||||
('gateway', gateway),
|
||||
)
|
||||
|
||||
|
||||
def create_cas_logout_url(cas_url, cas_route, url=None):
|
||||
""" Create a CAS logout URL.
|
||||
|
||||
Keyword arguments:
|
||||
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
|
||||
cas_route -- The route where the CAS lives on server (ex. /cas/logout)
|
||||
url -- (ex. http://localhost:5000/login)
|
||||
|
||||
Example usage:
|
||||
>>> create_cas_logout_url(
|
||||
... 'http://sso.pdx.edu',
|
||||
... '/cas/logout',
|
||||
... 'http://localhost:5000',
|
||||
... )
|
||||
'http://sso.pdx.edu/cas/logout?url=http%3A%2F%2Flocalhost%3A5000'
|
||||
"""
|
||||
return create_url(
|
||||
cas_url,
|
||||
cas_route,
|
||||
('service', url),
|
||||
)
|
||||
|
||||
|
||||
def create_cas_validate_url(cas_url, cas_route, service, ticket,
|
||||
renew=None):
|
||||
""" Create a CAS validate URL.
|
||||
|
||||
Keyword arguments:
|
||||
cas_url -- The url to the CAS (ex. http://sso.pdx.edu)
|
||||
cas_route -- The route where the CAS lives on server (ex. /cas/validate)
|
||||
service -- (ex. http://localhost:5000/login)
|
||||
ticket -- (ex. 'ST-58274-x839euFek492ou832Eena7ee-cas')
|
||||
renew -- "true" or "false"
|
||||
|
||||
Example usage:
|
||||
>>> create_cas_validate_url(
|
||||
... 'http://sso.pdx.edu',
|
||||
... '/cas/validate',
|
||||
... 'http://localhost:5000/login',
|
||||
... 'ST-58274-x839euFek492ou832Eena7ee-cas'
|
||||
... )
|
||||
"""
|
||||
return create_url(
|
||||
cas_url,
|
||||
cas_route,
|
||||
('service', service),
|
||||
('ticket', ticket),
|
||||
('renew', renew),
|
||||
)
|
|
@ -0,0 +1,164 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
import bs4
|
||||
from six.moves.urllib_request import urlopen
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_login import login_user, logout_user
|
||||
from flask import current_app, session, request, url_for, redirect
|
||||
|
||||
from .cas_urls import create_cas_login_url
|
||||
from .cas_urls import create_cas_logout_url
|
||||
from .cas_urls import create_cas_validate_url
|
||||
|
||||
from api.models.account import UserCache
|
||||
|
||||
blueprint = Blueprint('cas', __name__)
|
||||
|
||||
|
||||
@blueprint.route('/api/sso/login')
|
||||
def login():
|
||||
"""
|
||||
This route has two purposes. First, it is used by the user
|
||||
to login. Second, it is used by the CAS to respond with the
|
||||
`ticket` after the user logs in successfully.
|
||||
|
||||
When the user accesses this url, they are redirected to the CAS
|
||||
to login. If the login was successful, the CAS will respond to this
|
||||
route with the ticket in the url. The ticket is then validated.
|
||||
If validation was successful the logged in username is saved in
|
||||
the user's session under the key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
if request.values.get("next"):
|
||||
session["next"] = request.values.get("next")
|
||||
|
||||
_service = url_for('cas.login', _external=True, next=session["next"]) \
|
||||
if session.get("next") else url_for('cas.login', _external=True)
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
_service)
|
||||
|
||||
if 'ticket' in request.args:
|
||||
session[cas_token_session_key] = request.args.get('ticket')
|
||||
|
||||
if request.args.get('ticket'):
|
||||
|
||||
if validate(request.args['ticket']):
|
||||
redirect_url = session.get("next") or \
|
||||
current_app.config.get("CAS_AFTER_LOGIN")
|
||||
username = session.get("CAS_USERNAME")
|
||||
user = UserCache.get(username)
|
||||
login_user(user)
|
||||
|
||||
session.permanent = True
|
||||
|
||||
else:
|
||||
del session[cas_token_session_key]
|
||||
redirect_url = create_cas_login_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGIN_ROUTE'],
|
||||
url_for('cas.login', _external=True),
|
||||
renew=True)
|
||||
current_app.logger.info("redirect to: {0}".format(redirect_url))
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@blueprint.route('/api/sso/logout')
|
||||
def logout():
|
||||
"""
|
||||
When the user accesses this route they are logged out.
|
||||
"""
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
|
||||
|
||||
cas_username_session_key in session and session.pop(cas_username_session_key)
|
||||
"acl" in session and session.pop("acl")
|
||||
"uid" in session and session.pop("uid")
|
||||
cas_token_session_key in session and session.pop(cas_token_session_key)
|
||||
"next" in session and session.pop("next")
|
||||
|
||||
redirect_url = create_cas_logout_url(
|
||||
current_app.config['CAS_SERVER'],
|
||||
current_app.config['CAS_LOGOUT_ROUTE'],
|
||||
url_for('cas.login', _external=True, next=request.referrer))
|
||||
|
||||
logout_user()
|
||||
|
||||
current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
def validate(ticket):
|
||||
"""
|
||||
Will attempt to validate the ticket. If validation fails, then False
|
||||
is returned. If validation is successful, then True is returned
|
||||
and the validated username is saved in the session under the
|
||||
key `CAS_USERNAME_SESSION_KEY`.
|
||||
"""
|
||||
|
||||
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
|
||||
|
||||
current_app.logger.debug("validating token {0}".format(ticket))
|
||||
|
||||
cas_validate_url = create_cas_validate_url(
|
||||
current_app.config['CAS_VALIDATE_SERVER'],
|
||||
current_app.config['CAS_VALIDATE_ROUTE'],
|
||||
url_for('cas.login', _external=True),
|
||||
ticket)
|
||||
|
||||
current_app.logger.debug("Making GET request to {0}".format(cas_validate_url))
|
||||
|
||||
try:
|
||||
response = urlopen(cas_validate_url).read()
|
||||
ticketid = _parse_tag(response, "cas:user")
|
||||
strs = [s.strip() for s in ticketid.split('|') if s.strip()]
|
||||
username, is_valid = None, False
|
||||
if len(strs) == 1:
|
||||
username = strs[0]
|
||||
is_valid = True
|
||||
user_info = json.loads(_parse_tag(response, "cas:other"))
|
||||
current_app.logger.info(user_info)
|
||||
except ValueError:
|
||||
current_app.logger.error("CAS returned unexpected result")
|
||||
is_valid = False
|
||||
return is_valid
|
||||
|
||||
if is_valid:
|
||||
current_app.logger.debug("valid")
|
||||
session[cas_username_session_key] = username
|
||||
user = UserCache.get(username)
|
||||
session["acl"] = dict(uid=user_info.get("uuid"),
|
||||
avatar=user.avatar if user else user_info.get("avatar"),
|
||||
userId=user_info.get("id"),
|
||||
userName=user_info.get("name"),
|
||||
nickName=user_info.get("nickname"),
|
||||
parentRoles=user_info.get("parents"),
|
||||
childRoles=user_info.get("children"),
|
||||
roleName=user_info.get("role"))
|
||||
session["uid"] = user_info.get("uuid")
|
||||
current_app.logger.debug(session)
|
||||
current_app.logger.debug(request.url)
|
||||
else:
|
||||
current_app.logger.debug("invalid")
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
def _parse_tag(string, tag):
|
||||
"""
|
||||
Used for parsing xml. Search string for the first occurence of
|
||||
<tag>.....</tag> and return text (stripped of leading and tailing
|
||||
whitespace) between tags. Return "" if tag not found.
|
||||
"""
|
||||
soup = bs4.BeautifulSoup(string)
|
||||
|
||||
if soup.find(tag) is None:
|
||||
return ''
|
||||
return soup.find(tag).string.strip()
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -0,0 +1,150 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import db
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.const import type_map
|
||||
from api.lib.decorator import kwargs_required
|
||||
|
||||
|
||||
class AttributeManager(object):
|
||||
"""
|
||||
CI attributes manager
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_choice_values(attr_id, value_type):
|
||||
choice_table = type_map.get("choice").get(value_type)
|
||||
choice_values = choice_table.get_by(fl=["value"], attr_id=attr_id)
|
||||
return [choice_value["value"] for choice_value in choice_values]
|
||||
|
||||
@staticmethod
|
||||
def _add_choice_values(_id, value_type, choice_values):
|
||||
choice_table = type_map.get("choice").get(value_type)
|
||||
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete()
|
||||
db.session.flush()
|
||||
choice_values = choice_values
|
||||
for v in choice_values:
|
||||
table = choice_table(attr_id=_id, value=v)
|
||||
db.session.add(table)
|
||||
db.session.flush()
|
||||
|
||||
def get_attributes(self, name=None):
|
||||
"""
|
||||
:param name:
|
||||
:return: attribute, if name is None, then return all attributes
|
||||
"""
|
||||
attrs = Attribute.get_by_like(name=name) if name is not None else Attribute.get_by()
|
||||
res = list()
|
||||
for attr in attrs:
|
||||
attr["is_choice"] and attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
res.append(attr)
|
||||
return res
|
||||
|
||||
def get_attribute_by_name(self, name):
|
||||
attr = Attribute.get_by(name=name, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_alias(self, alias):
|
||||
attr = Attribute.get_by(alias=alias, first=True)
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
return attr
|
||||
|
||||
def get_attribute_by_id(self, _id):
|
||||
attr = Attribute.get_by_id(_id).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
return attr
|
||||
|
||||
def get_attribute(self, key):
|
||||
attr = AttributeCache.get(key).to_dict()
|
||||
if attr and attr["is_choice"]:
|
||||
attr.update(dict(choice_value=self.get_choice_values(attr["id"], attr["value_type"])))
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
choice_value = kwargs.pop("choice_value", [])
|
||||
kwargs.pop("is_choice", None)
|
||||
is_choice = True if choice_value else False
|
||||
name = kwargs.pop("name")
|
||||
alias = kwargs.pop("alias", "")
|
||||
alias = name if not alias else alias
|
||||
Attribute.get_by(name=name, first=True) and abort(400, "attribute {0} is already existed".format(name))
|
||||
|
||||
attr = Attribute.create(flush=True,
|
||||
name=name,
|
||||
alias=alias,
|
||||
is_choice=is_choice,
|
||||
**kwargs)
|
||||
|
||||
if choice_value:
|
||||
cls._add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add attribute error, {0}".format(str(e)))
|
||||
return abort(400, "add attribute <{0}> failed".format(name))
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
return attr.id
|
||||
|
||||
def update(self, _id, **kwargs):
|
||||
attr = Attribute.get_by_id(_id) or abort(404, "Attribute <{0}> does not exist".format(_id))
|
||||
|
||||
choice_value = kwargs.pop("choice_value", False)
|
||||
is_choice = True if choice_value else False
|
||||
|
||||
attr.update(flush=True, **kwargs)
|
||||
|
||||
if is_choice:
|
||||
self._add_choice_values(attr.id, attr.value_type, choice_value)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("update attribute error, {0}".format(str(e)))
|
||||
return abort(400, "update attribute <{0}> failed".format(_id))
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
return attr.id
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
attr = Attribute.get_by_id(_id) or abort(404, "Attribute <{0}> does not exist".format(_id))
|
||||
name = attr.name
|
||||
|
||||
if attr.is_choice:
|
||||
choice_table = type_map["choice"].get(attr.value_type)
|
||||
db.session.query(choice_table).filter(choice_table.attr_id == _id).delete() # FIXME: session conflict
|
||||
db.session.flush()
|
||||
|
||||
AttributeCache.clean(attr)
|
||||
|
||||
attr.soft_delete()
|
||||
|
||||
for i in CITypeAttribute.get_by(attr_id=_id, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
for i in PreferenceShowAttributes.get_by(attr_id=_id, to_dict=False):
|
||||
i.soft_delete()
|
||||
|
||||
return name
|
|
@ -0,0 +1,138 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import six
|
||||
if six.PY2:
|
||||
import sys
|
||||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
from api.extensions import cache
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
class AttributeCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
attr = cache.get('Field::Name::{0}'.format(key)) \
|
||||
or cache.get('Field::ID::{0}'.format(key)) \
|
||||
or cache.get('Field::Alias::{0}'.format(key))
|
||||
|
||||
if attr is None:
|
||||
attr = Attribute.get_by(name=key, first=True, to_dict=False) \
|
||||
or Attribute.get_by_id(key) \
|
||||
or Attribute.get_by(alias=key, first=True, to_dict=False)
|
||||
if attr is not None:
|
||||
cls.set(attr)
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
def set(cls, attr):
|
||||
cache.set('Field::ID::{0}'.format(attr.id), attr)
|
||||
cache.set('Field::Name::{0}'.format(attr.name), attr)
|
||||
cache.set('Field::Alias::{0}'.format(attr.alias), attr)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, attr):
|
||||
cache.delete('Field::ID::{0}'.format(attr.id))
|
||||
cache.delete('Field::Name::{0}'.format(attr.name))
|
||||
cache.delete('Field::Alias::{0}'.format(attr.alias))
|
||||
|
||||
|
||||
class CITypeCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
ct = cache.get("CIType::ID::{0}".format(key)) or \
|
||||
cache.get("CIType::Name::{0}".format(key)) or \
|
||||
cache.get("CIType::Alias::{0}".format(key))
|
||||
if ct is None:
|
||||
ct = CIType.get_by(name=key, first=True, to_dict=False) or \
|
||||
CIType.get_by_id(key) or \
|
||||
CIType.get_by(alias=key, first=True, to_dict=False)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
def set(cls, ct):
|
||||
cache.set("CIType::Name::{0}".format(ct.name), ct)
|
||||
cache.set("CIType::ID::{0}".format(ct.id), ct)
|
||||
cache.set("CIType::Alias::{0}".format(ct.alias), ct)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ct = cls.get(key)
|
||||
if ct is not None:
|
||||
cache.delete("CIType::Name::{0}".format(ct.name))
|
||||
cache.delete("CIType::ID::{0}".format(ct.id))
|
||||
cache.delete("CIType::Alias::{0}".format(ct.alias))
|
||||
|
||||
|
||||
class RelationTypeCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
ct = cache.get("RelationType::ID::{0}".format(key)) or \
|
||||
cache.get("RelationType::Name::{0}".format(key))
|
||||
if ct is None:
|
||||
ct = RelationType.get_by(name=key, first=True, to_dict=False) or RelationType.get_by_id(key)
|
||||
if ct is not None:
|
||||
cls.set(ct)
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
def set(cls, ct):
|
||||
cache.set("RelationType::Name::{0}".format(ct.name), ct)
|
||||
cache.set("RelationType::ID::{0}".format(ct.id), ct)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ct = cls.get(key)
|
||||
if ct is not None:
|
||||
cache.delete("RelationType::Name::{0}".format(ct.name))
|
||||
cache.delete("RelationType::ID::{0}".format(ct.id))
|
||||
|
||||
|
||||
class CITypeAttributeCache(object):
|
||||
"""
|
||||
key is type_id or type_name
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
|
||||
attrs = cache.get("CITypeAttribute::Name::{0}".format(key)) \
|
||||
or cache.get("CITypeAttribute::ID::{0}".format(key))
|
||||
if not attrs:
|
||||
attrs = CITypeAttribute.get_by(type_id=key, to_dict=False)
|
||||
if not attrs:
|
||||
ci_type = CIType.get_by(name=key, first=True, to_dict=False)
|
||||
if ci_type is not None:
|
||||
attrs = CITypeAttribute.get_by(type_id=ci_type.id, to_dict=False)
|
||||
if attrs is not None:
|
||||
cls.set(key, attrs)
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, values):
|
||||
ci_type = CITypeCache.get(key)
|
||||
if ci_type is not None:
|
||||
cache.set("CITypeAttribute::ID::{0}".format(ci_type.id), values)
|
||||
cache.set("CITypeAttribute::Name::{0}".format(ci_type.name), values)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ci_type = CITypeCache.get(key)
|
||||
attrs = cls.get(key)
|
||||
if attrs is not None and ci_type:
|
||||
cache.delete("CITypeAttribute::ID::{0}".format(ci_type.id))
|
||||
cache.delete("CITypeAttribute::Name::{0}".format(ci_type.name))
|
|
@ -0,0 +1,552 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import RelationTypeCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.const import TableMap
|
||||
from api.lib.cmdb.const import type_map
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.cmdb.history import CIRelationHistoryManager
|
||||
from api.lib.cmdb.query_sql import QUERY_CIS_BY_IDS
|
||||
from api.lib.cmdb.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||
from api.lib.cmdb.value import AttributeValueManager
|
||||
from api.lib.decorator import kwargs_required
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.models.cmdb import CIRelation
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.tasks.cmdb import ci_delete
|
||||
|
||||
|
||||
class CIManager(object):
|
||||
""" manage CI interface
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_type_name(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
return CITypeCache.get(ci.type_id).name
|
||||
|
||||
@staticmethod
|
||||
def confirm_ci_existed(ci_id):
|
||||
CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
|
||||
@classmethod
|
||||
def get_ci_by_id(cls, ci_id, ret_key=RetKey.NAME, fields=None, need_children=True):
|
||||
"""
|
||||
|
||||
:param ci_id:
|
||||
:param ret_key: name, id, or alias
|
||||
:param fields: attribute list
|
||||
:param need_children:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
|
||||
res = dict()
|
||||
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
res.update(cls.get_cis_by_ids([str(ci_id)], fields=fields, ret_key=ret_key))
|
||||
|
||||
res['_type'] = ci_type.id
|
||||
res['_id'] = ci_id
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_ci_by_id_from_db(ci_id, ret_key=RetKey.NAME, fields=None, need_children=True, use_master=False):
|
||||
"""
|
||||
|
||||
:param ci_id:
|
||||
:param ret_key: name, id or alias
|
||||
:param fields: list
|
||||
:param need_children:
|
||||
:param use_master: whether to use master db
|
||||
:return:
|
||||
"""
|
||||
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
|
||||
|
||||
res = dict()
|
||||
|
||||
if need_children:
|
||||
children = CIRelationManager.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.name
|
||||
|
||||
fields = CITypeAttributeManager.get_attr_names_by_type_id(ci.type_id) if not fields else fields
|
||||
|
||||
_res = AttributeValueManager().get_attr_values(fields, ci_id, ret_key=ret_key, use_master=use_master)
|
||||
res.update(_res)
|
||||
|
||||
res['_type'] = ci_type.id
|
||||
res['_id'] = ci_id
|
||||
|
||||
return res
|
||||
|
||||
def get_ci_by_ids(self, ci_id_list, ret_key=RetKey.NAME, fields=None):
|
||||
return [self.get_ci_by_id(ci_id, ret_key=ret_key, fields=fields) for ci_id in ci_id_list]
|
||||
|
||||
@classmethod
|
||||
def get_cis_by_type(cls, type_id, ret_key=RetKey.NAME, fields="", page=1, per_page=None):
|
||||
cis = db.session.query(CI.id).filter(CI.type_id == type_id).filter(CI.deleted.is_(False))
|
||||
numfound = cis.count()
|
||||
|
||||
cis = cis.offset((page - 1) * per_page).limit(per_page)
|
||||
ci_ids = [str(ci.id) for ci in cis]
|
||||
res = cls.get_cis_by_ids(ci_ids, ret_key, fields)
|
||||
|
||||
return numfound, page, res
|
||||
|
||||
@staticmethod
|
||||
def ci_is_exist(unique_key, unique_value):
|
||||
"""
|
||||
|
||||
:param unique_key: is a attribute
|
||||
:param unique_value:
|
||||
:return:
|
||||
"""
|
||||
value_table = TableMap(attr_name=unique_key.name).table
|
||||
unique = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
if unique:
|
||||
return CI.get_by_id(unique.ci_id)
|
||||
|
||||
@staticmethod
|
||||
def _delete_ci_by_id(ci_id):
|
||||
ci = CI.get_by_id(ci_id)
|
||||
ci.delete() # TODO: soft delete
|
||||
|
||||
@classmethod
|
||||
def add(cls, ci_type_name, exist_policy=ExistPolicy.REPLACE, _no_attribute_policy=ExistPolicy.IGNORE, **ci_dict):
|
||||
"""
|
||||
|
||||
:param ci_type_name:
|
||||
:param exist_policy: replace or reject or need
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:param ci_dict:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ci_type = CITypeManager.check_is_existed(ci_type_name)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id) or abort(400, 'illegality unique attribute')
|
||||
|
||||
unique_value = ci_dict.get(unique_key.name) or \
|
||||
ci_dict.get(unique_key.alias) or \
|
||||
ci_dict.get(unique_key.id) or \
|
||||
abort(400, '{0} missing'.format(unique_key.name))
|
||||
|
||||
existed = cls.ci_is_exist(unique_key, unique_value)
|
||||
if existed is not None:
|
||||
if exist_policy == ExistPolicy.REJECT:
|
||||
return abort(400, 'CI is already existed')
|
||||
if existed.type_id != ci_type.id:
|
||||
existed.update(type_id=ci_type.id)
|
||||
ci = existed
|
||||
else:
|
||||
if exist_policy == ExistPolicy.NEED:
|
||||
return abort(404, 'CI <{0}> does not exist'.format(unique_value))
|
||||
ci = CI.create(type_id=ci_type.id)
|
||||
|
||||
value_manager = AttributeValueManager()
|
||||
for p, v in ci_dict.items():
|
||||
try:
|
||||
value_manager.create_or_update_attr_value(p, v, ci.id, _no_attribute_policy)
|
||||
except BadRequest as e:
|
||||
if existed is None:
|
||||
cls.delete(ci.id)
|
||||
raise e
|
||||
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
return ci.id
|
||||
|
||||
def update(self, ci_id, **ci_dict):
|
||||
self.confirm_ci_existed(ci_id)
|
||||
value_manager = AttributeValueManager()
|
||||
for p, v in ci_dict.items():
|
||||
try:
|
||||
value_manager.create_or_update_attr_value(p, v, ci_id)
|
||||
except BadRequest as e:
|
||||
raise e
|
||||
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def update_unique_value(ci_id, unique_name, unique_value):
|
||||
CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id))
|
||||
|
||||
AttributeValueManager().create_or_update_attr_value(unique_name, unique_value, ci_id)
|
||||
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
|
||||
@staticmethod
|
||||
def delete(ci_id):
|
||||
ci = CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not found".format(ci_id))
|
||||
|
||||
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
|
||||
attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
|
||||
for attr_name in attr_names:
|
||||
value_table = TableMap(attr_name=attr_name).table
|
||||
for item in value_table.get_by(ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
|
||||
for item in CIRelation.get_by(first_ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
|
||||
for item in CIRelation.get_by(second_ci_id=ci_id, to_dict=False):
|
||||
item.delete()
|
||||
|
||||
ci.delete() # TODO: soft delete
|
||||
|
||||
AttributeHistoryManger.add(ci_id, [(None, OperateType.DELETE, None, None)])
|
||||
|
||||
ci_delete.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
|
||||
return ci_id
|
||||
|
||||
@staticmethod
|
||||
def add_heartbeat(ci_type, unique_value):
|
||||
ci_type = CITypeManager().check_is_existed(ci_type)
|
||||
|
||||
unique_key = AttributeCache.get(ci_type.unique_id)
|
||||
value_table = TableMap(attr_name=unique_key.name).table
|
||||
|
||||
v = value_table.get_by(attr_id=unique_key.id,
|
||||
value=unique_value,
|
||||
to_dict=False,
|
||||
first=True) \
|
||||
or abort(404, "not found")
|
||||
|
||||
ci = CI.get_by_id(v.ci_id) or abort(404, "CI <{0}> is not found".format(v.ci_id))
|
||||
|
||||
ci.update(heartbeat=datetime.datetime.now())
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("type_id", "page")
|
||||
def get_heartbeat(cls, **kwargs):
|
||||
query = db.session.query(CI.id, CI.heartbeat).filter(CI.deleted.is_(False))
|
||||
|
||||
expire = datetime.datetime.now() - datetime.timedelta(minutes=72)
|
||||
type_ids = handle_arg_list(kwargs["type_id"])
|
||||
|
||||
query = query.filter(CI.type_id.in_(type_ids))
|
||||
|
||||
page = kwargs.get("page")
|
||||
agent_status = kwargs.get("agent_status")
|
||||
if agent_status == -1:
|
||||
query = query.filter(CI.heartbeat.is_(None))
|
||||
elif agent_status == 0:
|
||||
query = query.filter(CI.heartbeat <= expire)
|
||||
elif agent_status == 1:
|
||||
query = query.filter(CI.heartbeat > expire)
|
||||
|
||||
numfound = query.count()
|
||||
per_page_count = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
cis = query.offset((page - 1) * per_page_count).limit(per_page_count).all()
|
||||
ci_ids = [ci.id for ci in cis]
|
||||
heartbeat_dict = {}
|
||||
for ci in cis:
|
||||
if agent_status is not None:
|
||||
heartbeat_dict[ci.id] = agent_status
|
||||
else:
|
||||
if ci.heartbeat is None:
|
||||
heartbeat_dict[ci.id] = -1
|
||||
elif ci.heartbeat <= expire:
|
||||
heartbeat_dict[ci.id] = 0
|
||||
else:
|
||||
heartbeat_dict[ci.id] = 1
|
||||
current_app.logger.debug(heartbeat_dict)
|
||||
ci_ids = list(map(str, ci_ids))
|
||||
res = cls.get_cis_by_ids(ci_ids, fields=["hostname", "private_ip"])
|
||||
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
||||
heartbeat_dict.get(i.get("_id"))) for i in res
|
||||
if i.get("private_ip")]
|
||||
return numfound, result
|
||||
|
||||
@staticmethod
|
||||
def _get_cis_from_cache(ci_ids, ret_key=RetKey.NAME, fields=None):
|
||||
res = rd.get(ci_ids)
|
||||
if res is not None and None not in res and ret_key == RetKey.NAME:
|
||||
res = list(map(json.loads, res))
|
||||
if not fields:
|
||||
return res
|
||||
else:
|
||||
_res = []
|
||||
for d in res:
|
||||
_d = dict()
|
||||
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
||||
_d["ci_type"] = d.get("ci_type")
|
||||
for field in fields:
|
||||
_d[field] = d.get(field)
|
||||
_res.append(_d)
|
||||
return _res
|
||||
|
||||
@staticmethod
|
||||
def _get_cis_from_db(ci_ids, ret_key=RetKey.NAME, fields=None, value_tables=None):
|
||||
if not fields:
|
||||
filter_fields_sql = ""
|
||||
else:
|
||||
_fields = list()
|
||||
for field in fields:
|
||||
attr = AttributeCache.get(field)
|
||||
if attr is not None:
|
||||
_fields.append(str(attr.id))
|
||||
filter_fields_sql = "WHERE A.attr_id in ({0})".format(",".join(_fields))
|
||||
|
||||
ci_ids = ",".join(ci_ids)
|
||||
if value_tables is None:
|
||||
value_tables = type_map["table_name"].values()
|
||||
|
||||
value_sql = " UNION ".join([QUERY_CIS_BY_VALUE_TABLE.format(value_table, ci_ids)
|
||||
for value_table in value_tables])
|
||||
query_sql = QUERY_CIS_BY_IDS.format(filter_fields_sql, value_sql)
|
||||
# current_app.logger.debug(query_sql)
|
||||
cis = db.session.execute(query_sql).fetchall()
|
||||
ci_set = set()
|
||||
res = list()
|
||||
ci_dict = dict()
|
||||
for ci_id, type_id, attr_id, attr_name, attr_alias, value, value_type, is_list in cis:
|
||||
if ci_id not in ci_set:
|
||||
ci_dict = dict()
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
ci_dict["_id"] = ci_id
|
||||
ci_dict["_type"] = type_id
|
||||
ci_dict["ci_type"] = ci_type.name
|
||||
ci_dict["ci_type_alias"] = ci_type.alias
|
||||
ci_set.add(ci_id)
|
||||
res.append(ci_dict)
|
||||
|
||||
if ret_key == RetKey.NAME:
|
||||
attr_key = attr_name
|
||||
elif ret_key == RetKey.ALIAS:
|
||||
attr_key = attr_alias
|
||||
elif ret_key == RetKey.ID:
|
||||
attr_key = attr_id
|
||||
else:
|
||||
return abort(400, "invalid ret key")
|
||||
|
||||
value = type_map["serialize2"][value_type](value)
|
||||
if is_list:
|
||||
ci_dict.setdefault(attr_key, []).append(value)
|
||||
else:
|
||||
ci_dict[attr_key] = value
|
||||
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_cis_by_ids(cls, ci_ids, ret_key=RetKey.NAME, fields=None, value_tables=None):
|
||||
"""
|
||||
|
||||
:param ci_ids: list of CI instance ID, eg. ['1', '2']
|
||||
:param ret_key: name, id or alias
|
||||
:param fields:
|
||||
:param value_tables:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not ci_ids:
|
||||
return []
|
||||
|
||||
fields = [] if fields is None or not isinstance(fields, list) else fields
|
||||
|
||||
ci_id_tuple = tuple(map(int, ci_ids))
|
||||
res = cls._get_cis_from_cache(ci_id_tuple, ret_key, fields)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
current_app.logger.warning("cache not hit...............")
|
||||
return cls._get_cis_from_db(ci_ids, ret_key, fields, value_tables)
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
Manage relation between CIs
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _get_default_relation_type():
|
||||
return RelationTypeCache.get("contain").id # FIXME
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, ci_id, ret_key=RetKey.NAME):
|
||||
second_cis = CIRelation.get_by(first_ci_id=ci_id, to_dict=False)
|
||||
second_ci_ids = (second_ci.second_ci_id for second_ci in second_cis)
|
||||
ci_type2ci_ids = dict()
|
||||
for ci_id in second_ci_ids:
|
||||
type_id = CI.get_by_id(ci_id).type_id
|
||||
ci_type2ci_ids.setdefault(type_id, []).append(ci_id)
|
||||
|
||||
res = {}
|
||||
for type_id in ci_type2ci_ids:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
children = CIManager.get_cis_by_ids(list(map(str, ci_type2ci_ids[type_id])), ret_key=ret_key)
|
||||
res[ci_type.name] = children
|
||||
return res
|
||||
|
||||
def get_second_cis(self, first_ci_id, relation_type_id=None, page=1, per_page=None, **kwargs):
|
||||
second_cis = db.session.query(CI.id).filter(CI.deleted.is_(False)).join(
|
||||
CIRelation, CIRelation.second_ci_id == CI.id).filter(
|
||||
CIRelation.first_ci_id == first_ci_id)
|
||||
|
||||
if relation_type_id is not None:
|
||||
second_cis = second_cis.filter(CIRelation.relation_type_id == relation_type_id)
|
||||
|
||||
if kwargs: # TODO: special for devices
|
||||
second_cis = self._query_wrap_for_device(second_cis, **kwargs)
|
||||
|
||||
numfound = second_cis.count()
|
||||
if per_page != "all":
|
||||
second_cis = second_cis.offset((page - 1) * per_page).limit(per_page).all()
|
||||
ci_ids = [str(son.id) for son in second_cis]
|
||||
result = CIManager.get_cis_by_ids(ci_ids)
|
||||
|
||||
return numfound, len(ci_ids), result
|
||||
|
||||
@staticmethod
|
||||
def _sort_handler(sort_by, query_sql):
|
||||
|
||||
if sort_by.startswith("+"):
|
||||
sort_type = "asc"
|
||||
sort_by = sort_by[1:]
|
||||
elif sort_by.startswith("-"):
|
||||
sort_type = "desc"
|
||||
sort_by = sort_by[1:]
|
||||
else:
|
||||
sort_type = "asc"
|
||||
attr = AttributeCache.get(sort_by)
|
||||
if attr is None:
|
||||
return query_sql
|
||||
|
||||
attr_id = attr.id
|
||||
value_table = TableMap(attr_name=sort_by).table
|
||||
|
||||
ci_table = query_sql.subquery()
|
||||
query_sql = db.session.query(ci_table.c.id, value_table.value).join(
|
||||
value_table, value_table.ci_id == ci_table.c.id).filter(
|
||||
value_table.attr_id == attr_id).filter(ci_table.deleted.is_(False)).order_by(
|
||||
getattr(value_table.value, sort_type)())
|
||||
|
||||
return query_sql
|
||||
|
||||
def _query_wrap_for_device(self, query_sql, **kwargs):
|
||||
_type = kwargs.pop("_type", False) or kwargs.pop("type", False) or kwargs.pop("ci_type", False)
|
||||
if _type:
|
||||
ci_type = CITypeCache.get(_type)
|
||||
if ci_type is None:
|
||||
return
|
||||
query_sql = query_sql.filter(CI.type_id == ci_type.id)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
attr = AttributeCache.get(k)
|
||||
if attr is None:
|
||||
continue
|
||||
|
||||
value_table = TableMap(attr_name=k).table
|
||||
ci_table = query_sql.subquery()
|
||||
query_sql = db.session.query(ci_table.c.id).join(
|
||||
value_table, value_table.ci_id == ci_table.c.id).filter(
|
||||
value_table.attr_id == attr.id).filter(ci_table.deleted.is_(False)).filter(
|
||||
value_table.value.ilike(v.replace("*", "%")))
|
||||
|
||||
# current_app.logger.debug(query_sql)
|
||||
sort_by = kwargs.pop("sort", "")
|
||||
if sort_by:
|
||||
query_sql = self._sort_handler(sort_by, query_sql)
|
||||
|
||||
return query_sql
|
||||
|
||||
@classmethod
|
||||
def get_first_cis(cls, second_ci, relation_type_id=None, page=1, per_page=None):
|
||||
first_cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id == second_ci).filter(CIRelation.deleted.is_(False))
|
||||
if relation_type_id is not None:
|
||||
first_cis = first_cis.filter(CIRelation.relation_type_id == relation_type_id)
|
||||
|
||||
numfound = first_cis.count()
|
||||
if per_page != "all":
|
||||
first_cis = first_cis.offset((page - 1) * per_page).limit(per_page).all()
|
||||
|
||||
first_ci_ids = [str(first_ci.first_ci_id) for first_ci in first_cis]
|
||||
result = CIManager.get_cis_by_ids(first_ci_ids)
|
||||
|
||||
return numfound, len(first_ci_ids), result
|
||||
|
||||
@classmethod
|
||||
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
|
||||
|
||||
relation_type_id = relation_type_id or cls._get_default_relation_type()
|
||||
|
||||
CIManager.confirm_ci_existed(first_ci_id)
|
||||
CIManager.confirm_ci_existed(second_ci_id)
|
||||
|
||||
existed = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
if existed is not None:
|
||||
if existed.relation_type_id != relation_type_id:
|
||||
existed.update(relation_type_id=relation_type_id)
|
||||
CIRelationHistoryManager().add(existed, OperateType.UPDATE)
|
||||
else:
|
||||
existed = CIRelation.create(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
relation_type_id=relation_type_id)
|
||||
CIRelationHistoryManager().add(existed, OperateType.ADD)
|
||||
if more is not None:
|
||||
existed.upadte(more=more)
|
||||
|
||||
return existed.id
|
||||
|
||||
@staticmethod
|
||||
def delete(cr_id):
|
||||
cr = CIRelation.get_by_id(cr_id) or abort(404, "CIRelation <{0}> is not existed".format(cr_id))
|
||||
cr.soft_delete()
|
||||
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr, operate_type=OperateType.DELETE)
|
||||
|
||||
return cr_id
|
||||
|
||||
@classmethod
|
||||
def delete_2(cls, first_ci_id, second_ci_id):
|
||||
cr = CIRelation.get_by(first_ci_id=first_ci_id,
|
||||
second_ci_id=second_ci_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
return cls.delete(cr.cr_id)
|
|
@ -0,0 +1,392 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.models.cmdb import CIType
|
||||
from api.models.cmdb import CITypeGroup
|
||||
from api.models.cmdb import CITypeGroupItem
|
||||
from api.models.cmdb import CITypeRelation
|
||||
from api.models.cmdb import CITypeAttributeGroup
|
||||
from api.models.cmdb import CITypeAttributeGroupItem
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.decorator import kwargs_required
|
||||
|
||||
|
||||
class CITypeManager(object):
|
||||
"""
|
||||
manage CIType
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_name_by_id(type_id):
|
||||
return CITypeCache.get(type_id).name
|
||||
|
||||
@staticmethod
|
||||
def check_is_existed(key):
|
||||
return CITypeCache.get(key) or abort(404, "CIType <{0}> is not existed".format(key))
|
||||
|
||||
@staticmethod
|
||||
def get_ci_types(type_name=None):
|
||||
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
|
||||
res = list()
|
||||
for type_dict in ci_types:
|
||||
type_dict["unique_key"] = AttributeCache.get(type_dict["unique_id"]).name
|
||||
res.append(type_dict)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def query(_type):
|
||||
ci_type = CITypeCache.get(_type) or abort(404, "CIType <{0}> is not found".format(_type))
|
||||
return ci_type.to_dict()
|
||||
|
||||
@classmethod
|
||||
@kwargs_required("name")
|
||||
def add(cls, **kwargs):
|
||||
unique_key = kwargs.pop("unique_key", None)
|
||||
unique_key = AttributeCache.get(unique_key) or abort(404, "Unique key is not defined")
|
||||
|
||||
CIType.get_by(name=kwargs['name'], first=True) and \
|
||||
abort(404, "CIType <{0}> is already existed".format(kwargs.get("name")))
|
||||
|
||||
kwargs["alias"] = kwargs["name"] if not kwargs.get("alias") else kwargs["alias"]
|
||||
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
ci_type = CIType.create(**kwargs)
|
||||
|
||||
CITypeAttributeManager.add(ci_type.id, [unique_key.id], is_required=True)
|
||||
|
||||
CITypeCache.clean(ci_type.name)
|
||||
|
||||
return ci_type.id
|
||||
|
||||
@classmethod
|
||||
def update(cls, type_id, **kwargs):
|
||||
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
|
||||
unique_key = kwargs.pop("unique_key", None)
|
||||
unique_key = AttributeCache.get(unique_key)
|
||||
if unique_key is not None:
|
||||
kwargs["unique_id"] = unique_key.id
|
||||
type_attr = CITypeAttribute.get_by(type_id=type_id,
|
||||
attr_id=unique_key.id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if type_attr is None:
|
||||
CITypeAttributeManager.add(type_id, [unique_key.id], is_required=True)
|
||||
|
||||
ci_type.update(**kwargs)
|
||||
|
||||
CITypeCache.clean(type_id)
|
||||
|
||||
return type_id
|
||||
|
||||
@classmethod
|
||||
def set_enabled(cls, type_id, enabled=True):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
ci_type.update(enabled=enabled)
|
||||
return type_id
|
||||
|
||||
@classmethod
|
||||
def delete(cls, type_id):
|
||||
ci_type = cls.check_is_existed(type_id)
|
||||
ci_type.soft_delete()
|
||||
|
||||
CITypeCache.clean(type_id)
|
||||
|
||||
|
||||
class CITypeGroupManager(object):
|
||||
@staticmethod
|
||||
def get(need_other=None):
|
||||
groups = CITypeGroup.get_by()
|
||||
group_types = set()
|
||||
for group in groups:
|
||||
for t in sorted(CITypeGroupItem.get_by(group_id=group['id']), key=lambda x: x['order']):
|
||||
group.setdefault("ci_types", []).append(CITypeCache.get(t['type_id']).to_dict())
|
||||
group_types.add(t["type_id"])
|
||||
|
||||
if need_other:
|
||||
ci_types = CITypeManager.get_ci_types()
|
||||
other_types = dict(ci_types=[ci_type for ci_type in ci_types if ci_type["id"] not in group_types])
|
||||
groups.append(other_types)
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def add(name):
|
||||
CITypeGroup.get_by(name=name, first=True) and abort(400, "Group {0} does exist".format(name))
|
||||
return CITypeGroup.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(gid, name, type_ids):
|
||||
"""
|
||||
update all
|
||||
:param gid:
|
||||
:param name:
|
||||
:param type_ids:
|
||||
:return:
|
||||
"""
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(404, "Group <{0}> does not exist".format(gid))
|
||||
if name is not None:
|
||||
existed.update(name=name)
|
||||
|
||||
for idx, type_id in enumerate(type_ids):
|
||||
|
||||
item = CITypeGroupItem.get_by(group_id=gid, type_id=type_id, first=True, to_dict=False)
|
||||
if item is not None:
|
||||
item.update(order=idx)
|
||||
else:
|
||||
CITypeGroupItem.create(group_id=gid, type_id=type_id, order=idx)
|
||||
|
||||
@staticmethod
|
||||
def delete(gid):
|
||||
existed = CITypeGroup.get_by_id(gid) or abort(404, "Group <{0}> does not exist".format(gid))
|
||||
|
||||
items = CITypeGroupItem.get_by(group_id=gid, to_dict=False)
|
||||
for item in items:
|
||||
item.soft_delete()
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
|
||||
class CITypeAttributeManager(object):
|
||||
"""
|
||||
manage CIType's attributes, include query, add, update, delete
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attr_names_by_type_id(type_id):
|
||||
return [AttributeCache.get(attr.attr_id).name for attr in CITypeAttributeCache.get(type_id)]
|
||||
|
||||
@staticmethod
|
||||
def get_attributes_by_type_id(type_id):
|
||||
attrs = CITypeAttributeCache.get(type_id)
|
||||
result = list()
|
||||
for attr in sorted(attrs, key=lambda x: (x.order, x.id)):
|
||||
attr_dict = AttributeManager().get_attribute(attr.attr_id)
|
||||
attr_dict["is_required"] = attr.is_required
|
||||
attr_dict["order"] = attr.order
|
||||
attr_dict["default_show"] = attr.default_show
|
||||
result.append(attr_dict)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _check(type_id, attr_ids):
|
||||
CITypeManager.check_is_existed(type_id)
|
||||
|
||||
if not attr_ids or not isinstance(attr_ids, list):
|
||||
return abort(400, "Attributes are required")
|
||||
|
||||
for attr_id in attr_ids:
|
||||
AttributeCache.get(attr_id) or abort(404, "Attribute <{0}> is not existed".format(attr_id))
|
||||
|
||||
@classmethod
|
||||
def add(cls, type_id, attr_ids=None, **kwargs):
|
||||
"""
|
||||
add attributes to CIType
|
||||
:param type_id:
|
||||
:param attr_ids: list
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
cls._check(type_id, attr_ids)
|
||||
|
||||
for attr_id in attr_ids:
|
||||
existed = CITypeAttribute.get_by(type_id=type_id,
|
||||
attr_id=attr_id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is not None:
|
||||
continue
|
||||
|
||||
current_app.logger.debug(attr_id)
|
||||
CITypeAttribute.create(type_id=type_id, attr_id=attr_id, **kwargs)
|
||||
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
|
||||
@classmethod
|
||||
def update(cls, type_id, attributes):
|
||||
"""
|
||||
update attributes to CIType
|
||||
:param type_id:
|
||||
:param attributes: list
|
||||
:return:
|
||||
"""
|
||||
cls._check(type_id, [i.get('attr_id') for i in attributes])
|
||||
|
||||
for attr in attributes:
|
||||
existed = CITypeAttribute.get_by(type_id=type_id,
|
||||
attr_id=attr.get("attr_id"),
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is None:
|
||||
continue
|
||||
|
||||
existed.update(**attr)
|
||||
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, type_id, attr_ids=None):
|
||||
"""
|
||||
delete attributes from CIType
|
||||
:param type_id:
|
||||
:param attr_ids: list
|
||||
:return:
|
||||
"""
|
||||
cls._check(type_id, attr_ids)
|
||||
|
||||
for attr_id in attr_ids:
|
||||
existed = CITypeAttribute.get_by(type_id=type_id,
|
||||
attr_id=attr_id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is not None:
|
||||
existed.soft_delete()
|
||||
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
|
||||
|
||||
class CITypeRelationManager(object):
|
||||
"""
|
||||
manage relation between CITypes
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _wrap_relation_type_dict(type_id, relation_inst):
|
||||
ci_type_dict = CITypeCache.get(type_id).to_dict()
|
||||
ci_type_dict["ctr_id"] = relation_inst.id
|
||||
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["id"])
|
||||
ci_type_dict["relation_type"] = relation_inst.relation_type.name
|
||||
return ci_type_dict
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, parent_id):
|
||||
children = CITypeRelation.get_by(parent_id=parent_id, to_dict=False)
|
||||
|
||||
return [cls._wrap_relation_type_dict(child.child_id, child) for child in children]
|
||||
|
||||
@classmethod
|
||||
def get_parents(cls, child_id):
|
||||
parents = CITypeRelation.get_by(child_id=child_id, to_dict=False)
|
||||
|
||||
return [cls._wrap_relation_type_dict(parent.parent_id, parent) for parent in parents]
|
||||
|
||||
@staticmethod
|
||||
def _get(parent_id, child_id):
|
||||
return CITypeRelation.get_by(parent_id=parent_id,
|
||||
child_id=child_id,
|
||||
to_dict=False,
|
||||
first=True)
|
||||
|
||||
@classmethod
|
||||
def add(cls, parent, child, relation_type_id):
|
||||
p = CITypeManager.check_is_existed(parent)
|
||||
c = CITypeManager.check_is_existed(child)
|
||||
|
||||
existed = cls._get(p.id, c.id)
|
||||
if existed is not None:
|
||||
existed.update(relation_type_id=relation_type_id)
|
||||
else:
|
||||
existed = CITypeRelation.create(parent_id=p.id,
|
||||
child_id=c.id,
|
||||
relation_type_id=relation_type_id)
|
||||
return existed.id
|
||||
|
||||
@staticmethod
|
||||
def delete(_id):
|
||||
ctr = CITypeRelation.get_by_id(_id) or abort(404, "Type relation <{0}> is not found".format(_id))
|
||||
ctr.soft_delete()
|
||||
|
||||
@classmethod
|
||||
def delete_2(cls, parent, child):
|
||||
ctr = cls._get(parent, child)
|
||||
return cls.delete(ctr.id)
|
||||
|
||||
|
||||
class CITypeAttributeGroupManager(object):
|
||||
@staticmethod
|
||||
def get_by_type_id(type_id, need_other=None):
|
||||
groups = CITypeAttributeGroup.get_by(type_id=type_id)
|
||||
groups = sorted(groups, key=lambda x: x["order"])
|
||||
grouped = list()
|
||||
for group in groups:
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
|
||||
items = sorted(items, key=lambda x: x.order)
|
||||
group["attributes"] = [AttributeCache.get(i.attr_id).to_dict() for i in items]
|
||||
grouped.extend([i.attr_id for i in items])
|
||||
|
||||
if need_other is not None:
|
||||
grouped = set(grouped)
|
||||
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
|
||||
other_attributes = [attr for attr in attributes if attr["id"] not in grouped]
|
||||
groups.append(dict(attributes=other_attributes))
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def create_or_update(type_id, name, attr_order, group_order=0):
|
||||
"""
|
||||
create or update
|
||||
:param type_id:
|
||||
:param name:
|
||||
:param group_order: group order
|
||||
:param attr_order:
|
||||
:return:
|
||||
"""
|
||||
existed = CITypeAttributeGroup.get_by(type_id=type_id, name=name, first=True, to_dict=False) \
|
||||
or CITypeAttributeGroup.create(type_id=type_id, name=name, order=group_order)
|
||||
existed.update(order=group_order)
|
||||
attr_order = dict(attr_order)
|
||||
current_app.logger.info(attr_order)
|
||||
existed_items = CITypeAttributeGroupItem.get_by(group_id=existed.id, to_dict=False)
|
||||
for item in existed_items:
|
||||
if item.attr_id not in attr_order:
|
||||
item.soft_delete()
|
||||
else:
|
||||
item.update(order=attr_order[item.attr_id])
|
||||
|
||||
existed_items = {item.attr_id: 1 for item in existed_items}
|
||||
for attr_id, order in attr_order.items():
|
||||
if attr_id not in existed_items:
|
||||
CITypeAttributeGroupItem.create(group_id=existed.id, attr_id=attr_id, order=order)
|
||||
|
||||
return existed
|
||||
|
||||
@classmethod
|
||||
def update(cls, group_id, name, attr_order, group_order=0):
|
||||
group = CITypeAttributeGroup.get_by_id(group_id) or abort(404, "Group <{0}> does not exist".format(group_id))
|
||||
other = CITypeAttributeGroup.get_by(type_id=group.type_id, name=name, first=True, to_dict=False)
|
||||
if other is not None and other.id != group.id:
|
||||
return abort(400, "Group <{0}> duplicate".format(name))
|
||||
if name is not None:
|
||||
group.update(name=name)
|
||||
|
||||
cls.create_or_update(group.type_id, name, attr_order, group_order)
|
||||
|
||||
@staticmethod
|
||||
def delete(group_id):
|
||||
group = CITypeAttributeGroup.get_by_id(group_id) \
|
||||
or abort(404, "AttributeGroup <{0}> does not exist".format(group_id))
|
||||
group.soft_delete()
|
||||
|
||||
items = CITypeAttributeGroupItem.get_by(group_id=group_id, to_dict=False)
|
||||
for item in items:
|
||||
item.soft_delete()
|
||||
|
||||
return group_id
|
|
@ -0,0 +1,148 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
from markupsafe import escape
|
||||
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import TextChoice
|
||||
from api.models.cmdb import FloatChoice
|
||||
from api.models.cmdb import IntegerChoice
|
||||
from api.models.cmdb import CIValueText
|
||||
from api.models.cmdb import CIValueInteger
|
||||
from api.models.cmdb import CIValueFloat
|
||||
from api.models.cmdb import CIValueDateTime
|
||||
from api.models.cmdb import CIIndexValueDateTime
|
||||
from api.models.cmdb import CIIndexValueFloat
|
||||
from api.models.cmdb import CIIndexValueInteger
|
||||
from api.models.cmdb import CIIndexValueText
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
|
||||
|
||||
def string2int(x):
|
||||
return int(float(x))
|
||||
|
||||
|
||||
def str2datetime(x):
|
||||
try:
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
type_map = {
|
||||
'deserialize': {
|
||||
Attribute.INT: string2int,
|
||||
Attribute.FLOAT: float,
|
||||
Attribute.TEXT: escape,
|
||||
Attribute.TIME: escape,
|
||||
Attribute.DATETIME: str2datetime,
|
||||
Attribute.DATE: str2datetime,
|
||||
},
|
||||
'serialize': {
|
||||
Attribute.INT: int,
|
||||
Attribute.FLOAT: float,
|
||||
Attribute.TEXT: str,
|
||||
Attribute.TIME: str,
|
||||
Attribute.DATE: lambda x: x.strftime("%Y%m%d"),
|
||||
Attribute.DATETIME: lambda x: x.strftime("%Y%m%d %H:%M:%S"),
|
||||
},
|
||||
'serialize2': {
|
||||
Attribute.INT: int,
|
||||
Attribute.FLOAT: float,
|
||||
Attribute.TEXT: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
Attribute.TIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
Attribute.DATE: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
Attribute.DATETIME: lambda x: x.decode() if not isinstance(x, six.string_types) else x,
|
||||
},
|
||||
'choice': {
|
||||
Attribute.INT: IntegerChoice,
|
||||
Attribute.FLOAT: FloatChoice,
|
||||
Attribute.TEXT: TextChoice,
|
||||
},
|
||||
'table': {
|
||||
Attribute.INT: CIValueInteger,
|
||||
Attribute.TEXT: CIValueText,
|
||||
Attribute.DATETIME: CIValueDateTime,
|
||||
Attribute.DATE: CIValueDateTime,
|
||||
Attribute.TIME: CIValueText,
|
||||
Attribute.FLOAT: CIValueFloat,
|
||||
'index_{0}'.format(Attribute.INT): CIIndexValueInteger,
|
||||
'index_{0}'.format(Attribute.TEXT): CIIndexValueText,
|
||||
'index_{0}'.format(Attribute.DATETIME): CIIndexValueDateTime,
|
||||
'index_{0}'.format(Attribute.DATE): CIIndexValueDateTime,
|
||||
'index_{0}'.format(Attribute.TIME): CIIndexValueText,
|
||||
'index_{0}'.format(Attribute.FLOAT): CIIndexValueFloat,
|
||||
},
|
||||
'table_name': {
|
||||
Attribute.INT: 'c_value_integers',
|
||||
Attribute.TEXT: 'c_value_texts',
|
||||
Attribute.DATETIME: 'c_value_datetime',
|
||||
Attribute.DATE: 'c_value_datetime',
|
||||
Attribute.TIME: 'c_value_texts',
|
||||
Attribute.FLOAT: 'c_value_floats',
|
||||
'index_{0}'.format(Attribute.INT): 'c_value_index_integers',
|
||||
'index_{0}'.format(Attribute.TEXT): 'c_value_index_texts',
|
||||
'index_{0}'.format(Attribute.DATETIME): 'c_value_index_datetime',
|
||||
'index_{0}'.format(Attribute.DATE): 'c_value_index_datetime',
|
||||
'index_{0}'.format(Attribute.TIME): 'c_value_index_texts',
|
||||
'index_{0}'.format(Attribute.FLOAT): 'c_value_index_floats',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TableMap(object):
|
||||
def __init__(self, attr_name=None):
|
||||
self.attr_name = attr_name
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
attr = AttributeCache.get(self.attr_name)
|
||||
i = "index_{0}".format(attr.value_type) if attr.is_index else attr.value_type
|
||||
return type_map["table"].get(i)
|
||||
|
||||
@property
|
||||
def table_name(self):
|
||||
attr = AttributeCache.get(self.attr_name)
|
||||
i = "index_{0}".format(attr.value_type) if attr.is_index else attr.value_type
|
||||
return type_map["table_name"].get(i)
|
||||
|
||||
|
||||
class ExistPolicy(object):
|
||||
REJECT = "reject"
|
||||
NEED = "need"
|
||||
IGNORE = "ignore"
|
||||
REPLACE = "replace"
|
||||
|
||||
|
||||
class OperateType(object):
|
||||
ADD = "0"
|
||||
DELETE = "1"
|
||||
UPDATE = "2"
|
||||
|
||||
|
||||
class RetKey(object):
|
||||
ID = "id"
|
||||
NAME = "name"
|
||||
ALIAS = "alias"
|
||||
|
||||
|
||||
class ResourceType(object):
|
||||
CI = "CIType"
|
||||
|
||||
|
||||
class PermEnum(object):
|
||||
ADD = "add"
|
||||
UPDATE = "update"
|
||||
DELETE = "delete"
|
||||
READ = "read"
|
||||
|
||||
|
||||
class RoleEnum(object):
|
||||
CONFIG = "admin"
|
||||
|
||||
CMDB_QUEUE = "cmdb_async"
|
||||
REDIS_PREFIX = "CMDB_CI"
|
|
@ -0,0 +1,126 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import g
|
||||
from flask import abort
|
||||
|
||||
|
||||
from api.extensions import db
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import OperationRecord
|
||||
from api.models.cmdb import AttributeHistory
|
||||
from api.models.cmdb import CIRelationHistory
|
||||
from api.models.account import UserCache
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import RelationTypeCache
|
||||
|
||||
|
||||
class AttributeHistoryManger(object):
|
||||
|
||||
@staticmethod
|
||||
def get_records(start, end, username, page, page_size):
|
||||
records = db.session.query(OperationRecord).filter(OperationRecord.deleted.is_(False))
|
||||
numfound = db.session.query(db.func.count(OperationRecord.id)).filter(OperationRecord.deleted.is_(False))
|
||||
if start:
|
||||
records = records.filter(OperationRecord.created_at >= start)
|
||||
numfound = numfound.filter(OperationRecord.created_at >= start)
|
||||
if end:
|
||||
records = records.filter(OperationRecord.created_at <= end)
|
||||
numfound = records.filter(OperationRecord.created_at <= end)
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user:
|
||||
records = records.filter(OperationRecord.uid == user.uid)
|
||||
else:
|
||||
return abort(404, "User <{0}> is not found".format(username))
|
||||
|
||||
records = records.order_by(-OperationRecord.id).offset(page_size * (page - 1)).limit(page_size).all()
|
||||
total = len(records)
|
||||
numfound = numfound.first()[0]
|
||||
res = []
|
||||
for record in records:
|
||||
_res = record.to_dict()
|
||||
_res["user"] = UserCache.get(_res.get("uid")).nickname or UserCache.get(_res.get("uid")).username
|
||||
|
||||
attr_history = AttributeHistory.get_by(record_id=_res.get("id"), to_dict=False)
|
||||
_res["attr_history"] = [AttributeCache.get(h.attr_id).attr_alias for h in attr_history]
|
||||
|
||||
rel_history = CIRelationHistory.get_by(record_id=_res.get("id"), to_dict=False)
|
||||
rel_statis = {}
|
||||
for rel in rel_history:
|
||||
if rel.operate_type not in rel_statis:
|
||||
rel_statis[rel.operate_type] = 1
|
||||
else:
|
||||
rel_statis[rel.operate_type] += 1
|
||||
_res["rel_history"] = rel_statis
|
||||
res.append(_res)
|
||||
|
||||
return numfound, total, res
|
||||
|
||||
@staticmethod
|
||||
def get_by_ci_id(ci_id):
|
||||
res = db.session.query(AttributeHistory, Attribute, OperationRecord).join(
|
||||
Attribute, Attribute.id == AttributeHistory.attr_id).join(
|
||||
OperationRecord, OperationRecord.id == AttributeHistory.record_id).filter(
|
||||
AttributeHistory.ci_id == ci_id).order_by(OperationRecord.id.desc())
|
||||
return [dict(attr_name=i.Attribute.name,
|
||||
attr_alias=i.Attribute.alias,
|
||||
operate_type=i.AttributeHistory.operate_type,
|
||||
username=UserCache.get(i.OperationRecord.uid).nickname,
|
||||
old=i.AttributeHistory.old,
|
||||
new=i.AttributeHistory.new,
|
||||
created_at=i.OperationRecord.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record_id=i.OperationRecord.id,
|
||||
hid=i.AttributeHistory.id
|
||||
) for i in res]
|
||||
|
||||
@staticmethod
|
||||
def get_record_detail(record_id):
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
|
||||
record = OperationRecord.get_by_id(record_id) or abort(404, "Record <{0}> is not found".format(record_id))
|
||||
|
||||
username = UserCache.get(record.uid).nickname or UserCache.get(record.uid).username
|
||||
timestamp = record.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
attr_history = AttributeHistory.get_By(record_id=record_id, to_dict=False)
|
||||
rel_history = CIRelationHistory.get_by(record_id=record_id, to_dict=False)
|
||||
|
||||
attr_dict, rel_dict = dict(), {"add": [], "delete": []}
|
||||
for attr_h in attr_history:
|
||||
attr_dict[AttributeCache.get(attr_h.attr_id).alias] = dict(
|
||||
old=attr_h.old,
|
||||
new=attr_h.new,
|
||||
operate_type=attr_h.operate_type)
|
||||
|
||||
for rel_h in rel_history:
|
||||
first = CIManager.get_ci_by_id(rel_h.first_ci_id)
|
||||
second = CIManager.get_ci_by_id(rel_h.second_ci_id)
|
||||
rel_dict[rel_h.operate_type].append((first, RelationTypeCache.get(rel_h.relation_type_id).name, second))
|
||||
|
||||
return username, timestamp, attr_dict, rel_dict
|
||||
|
||||
@staticmethod
|
||||
def add(ci_id, history_list):
|
||||
record = OperationRecord.create(uid=g.user.uid)
|
||||
|
||||
for attr_id, operate_type, old, new in history_list or []:
|
||||
AttributeHistory.create(attr_id=attr_id,
|
||||
operate_type=operate_type,
|
||||
old=old,
|
||||
new=new,
|
||||
ci_id=ci_id,
|
||||
record_id=record.id)
|
||||
|
||||
|
||||
class CIRelationHistoryManager(object):
|
||||
|
||||
@staticmethod
|
||||
def add(rel_obj, operate_type=CIRelationHistory.ADD):
|
||||
record = OperationRecord.create(uid=g.user.uid)
|
||||
|
||||
CIRelationHistory.create(relation_id=rel_obj.id,
|
||||
record_id=record.id,
|
||||
operate_type=operate_type,
|
||||
first_ci_id=rel_obj.first_ci_id,
|
||||
second_ci_id=rel_obj.second_ci_id,
|
||||
relation_type_id=rel_obj.relation_type_id)
|
|
@ -0,0 +1,137 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import six
|
||||
import toposort
|
||||
from flask import g
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.cache import CITypeAttributeCache
|
||||
from api.models.cmdb import PreferenceShowAttributes
|
||||
from api.models.cmdb import PreferenceTreeView
|
||||
from api.models.cmdb import PreferenceRelationView
|
||||
from api.models.cmdb import CITypeAttribute
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
|
||||
|
||||
class PreferenceManager(object):
|
||||
|
||||
@staticmethod
|
||||
def get_types(instance=False, tree=False):
|
||||
types = db.session.query(PreferenceShowAttributes.type_id).filter(
|
||||
PreferenceShowAttributes.uid == g.user.uid).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).group_by(PreferenceShowAttributes.type_id).all() \
|
||||
if instance else []
|
||||
tree_types = PreferenceTreeView.get_by(uid=g.user.uid, to_dict=False) if tree else []
|
||||
type_ids = list(set([i.type_id for i in types + tree_types]))
|
||||
return [CITypeCache.get(type_id).to_dict() for type_id in type_ids]
|
||||
|
||||
@staticmethod
|
||||
def get_show_attributes(type_id):
|
||||
if not isinstance(type_id, six.integer_types):
|
||||
type_id = CITypeCache.get(type_id).id
|
||||
|
||||
attrs = db.session.query(PreferenceShowAttributes, CITypeAttribute.order).join(
|
||||
CITypeAttribute, CITypeAttribute.attr_id == PreferenceShowAttributes.attr_id).filter(
|
||||
PreferenceShowAttributes.uid == g.user.uid).filter(
|
||||
PreferenceShowAttributes.type_id == type_id).filter(
|
||||
PreferenceShowAttributes.deleted.is_(False)).filter(CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.type_id == type_id).order_by(
|
||||
CITypeAttribute.order).all()
|
||||
result = [i.PreferenceShowAttributes.attr.to_dict() for i in attrs]
|
||||
is_subscribed = True
|
||||
if not attrs:
|
||||
attrs = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == type_id).filter(
|
||||
CITypeAttribute.deleted.is_(False)).filter(
|
||||
CITypeAttribute.default_show.is_(True)).order_by(CITypeAttribute.order)
|
||||
result = [i.attr.to_dict() for i in attrs]
|
||||
is_subscribed = False
|
||||
|
||||
for i in result:
|
||||
if i["is_choice"]:
|
||||
i.update(dict(choice_value=AttributeManager.get_choice_values(i["id"], i["value_type"])))
|
||||
|
||||
return is_subscribed, result
|
||||
|
||||
@classmethod
|
||||
def create_or_update_show_attributes(cls, type_id, attr_order):
|
||||
existed_all = PreferenceShowAttributes.get_by(type_id=type_id, uid=g.user.uid, to_dict=False)
|
||||
for _attr, order in attr_order:
|
||||
attr = AttributeCache.get(_attr) or abort(404, "Attribute <{0}> does not exist".format(_attr))
|
||||
existed = PreferenceShowAttributes.get_by(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
attr_id=attr.id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
if existed is None:
|
||||
PreferenceShowAttributes.create(type_id=type_id,
|
||||
uid=g.user.uid,
|
||||
attr_id=attr.id,
|
||||
order=order)
|
||||
else:
|
||||
existed.update(order=order)
|
||||
|
||||
attr_dict = {int(i): j for i, j in attr_order}
|
||||
for i in existed_all:
|
||||
if i.attr_id not in attr_dict:
|
||||
i.soft_delete()
|
||||
|
||||
@staticmethod
|
||||
def get_tree_view():
|
||||
res = PreferenceTreeView.get_by(uid=g.user.uid, to_dict=True)
|
||||
for item in res:
|
||||
if item["levels"]:
|
||||
item.update(CITypeCache.get(item['type_id']).to_dict())
|
||||
item.update(dict(levels=[AttributeCache.get(l).to_dict()
|
||||
for l in item["levels"].split(",") if AttributeCache.get(l)]))
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def create_or_update_tree_view(type_id, levels):
|
||||
attrs = CITypeAttributeCache.get(type_id)
|
||||
for idx, i in enumerate(levels):
|
||||
for attr in attrs:
|
||||
attr = AttributeCache.get(attr.attr_id)
|
||||
if i == attr.id or i == attr.name or i == attr.alias:
|
||||
levels[idx] = str(attr.id)
|
||||
levels = ",".join(levels)
|
||||
|
||||
existed = PreferenceTreeView.get_by(uid=g.user.uid, type_id=type_id, to_dict=False, first=True)
|
||||
if existed is not None:
|
||||
if not levels:
|
||||
existed.soft_delete()
|
||||
return existed
|
||||
return existed.update(levels=levels)
|
||||
elif levels:
|
||||
return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=g.user.uid)
|
||||
|
||||
@staticmethod
|
||||
def get_relation_view():
|
||||
views = PreferenceRelationView.get_by(to_dict=True)
|
||||
result = dict()
|
||||
for view in views:
|
||||
result.setdefault(view['name'], []).append(view)
|
||||
|
||||
for view_name in result:
|
||||
result[view_name] = toposort.toposort_flatten({i['child_id']: {i['parent_id']} for i in result[view_name]})
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def create_or_update_relation_view(name, parent_id, child_id):
|
||||
existed = PreferenceRelationView.get_by(name=name, parent_id=parent_id, child_id=child_id,
|
||||
to_dict=False, first=True)
|
||||
if existed is None:
|
||||
return PreferenceRelationView.create(name=name, parent_id=parent_id, child_id=child_id)
|
||||
|
||||
return existed
|
||||
|
||||
@staticmethod
|
||||
def delete_relation_view(name):
|
||||
for existed in PreferenceRelationView.get_by(name=name, to_dict=False):
|
||||
existed.soft_delete()
|
||||
return name
|
|
@ -0,0 +1,63 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
QUERY_CIS_BY_VALUE_TABLE = """
|
||||
SELECT attr.name AS attr_name,
|
||||
attr.alias AS attr_alias,
|
||||
attr.value_type,
|
||||
attr.is_list,
|
||||
c_cis.type_id,
|
||||
{0}.ci_id,
|
||||
{0}.attr_id,
|
||||
{0}.value
|
||||
FROM {0}
|
||||
INNER JOIN c_cis ON {0}.ci_id=c_cis.id
|
||||
AND {0}.`ci_id` IN ({1})
|
||||
INNER JOIN c_attributes as attr ON attr.id = {0}.attr_id
|
||||
"""
|
||||
|
||||
# {2}: value_table
|
||||
QUERY_CIS_BY_IDS = """
|
||||
SELECT A.ci_id,
|
||||
A.type_id,
|
||||
A.attr_id,
|
||||
A.attr_name,
|
||||
A.attr_alias,
|
||||
A.value,
|
||||
A.value_type,
|
||||
A.is_list
|
||||
FROM
|
||||
({1}) AS A {0}
|
||||
ORDER BY A.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY1 = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN c_attributes AS attr ON attr.id={0}.attr_id
|
||||
WHERE attr.name="{1}"
|
||||
GROUP BY {0}.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
||||
WHERE {0}.attr_id={2:d}
|
||||
GROUP BY {0}.value
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_ATTR_NAME = """
|
||||
SELECT {0}.ci_id
|
||||
FROM {0}
|
||||
WHERE {0}.attr_id={1:d}
|
||||
AND {0}.value {2}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
SELECT c_cis.id AS ci_id
|
||||
FROM c_cis
|
||||
WHERE c_cis.type_id in ({0})
|
||||
"""
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.models.cmdb import RelationType
|
||||
|
||||
|
||||
class RelationTypeManager(object):
|
||||
@staticmethod
|
||||
def get_all():
|
||||
return RelationType.get_by(to_dict=False)
|
||||
|
||||
@classmethod
|
||||
def get_names(cls):
|
||||
return [i.name for i in cls.get_all()]
|
||||
|
||||
@classmethod
|
||||
def get_pairs(cls):
|
||||
return [(i.id, i.name) for i in cls.get_all()]
|
||||
|
||||
@staticmethod
|
||||
def add(name):
|
||||
RelationType.get_by(name=name, first=True, to_dict=False) and abort(400, "It's already existed")
|
||||
return RelationType.create(name=name)
|
||||
|
||||
@staticmethod
|
||||
def update(rel_id, name):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(404, "RelationType <{0}> does not exist".format(rel_id))
|
||||
|
||||
return existed.update(name=name)
|
||||
|
||||
@staticmethod
|
||||
def delete(rel_id):
|
||||
existed = RelationType.get_by_id(rel_id) or abort(404, "RelationType <{0}> does not exist".format(rel_id))
|
||||
|
||||
existed.soft_delete()
|
|
@ -0,0 +1,356 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.const import TableMap
|
||||
from api.lib.cmdb.query_sql import FACET_QUERY
|
||||
from api.lib.cmdb.query_sql import QUERY_CI_BY_ATTR_NAME
|
||||
from api.lib.cmdb.query_sql import QUERY_CI_BY_TYPE
|
||||
from api.models.cmdb import Attribute
|
||||
from api.models.cmdb import CI
|
||||
|
||||
|
||||
class SearchError(Exception):
|
||||
def __init__(self, v):
|
||||
self.v = v
|
||||
|
||||
def __str__(self):
|
||||
return self.v
|
||||
|
||||
|
||||
class Search(object):
|
||||
def __init__(self, query=None, fl=None, facet_field=None, page=1, ret_key=RetKey.NAME, count=1, sort=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
self.page = page
|
||||
self.ret_key = ret_key
|
||||
self.count = count
|
||||
self.sort = sort
|
||||
self.query_sql = ""
|
||||
self.type_id_list = []
|
||||
self.only_type_query = False
|
||||
|
||||
@staticmethod
|
||||
def _operator_proc(key):
|
||||
operator = "&"
|
||||
if key.startswith("+"):
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("-"):
|
||||
operator = "|"
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("~"):
|
||||
operator = "~"
|
||||
key = key[1:].strip()
|
||||
|
||||
return operator, key
|
||||
|
||||
def _attr_name_proc(self, key):
|
||||
operator, key = self._operator_proc(key)
|
||||
|
||||
if key in ('ci_type', 'type', '_type'):
|
||||
return '_type', Attribute.TEXT, operator, None
|
||||
|
||||
if key in ('id', 'ci_id', '_id'):
|
||||
return '_id', Attribute.TEXT, operator, None
|
||||
|
||||
attr = AttributeCache.get(key)
|
||||
if attr:
|
||||
return attr.name, attr.value_type, operator, attr
|
||||
else:
|
||||
raise SearchError("{0} is not existed".format(key))
|
||||
|
||||
def _type_query_handler(self, v):
|
||||
new_v = v[1:-1].split(";") if v.startswith("(") and v.endswith(")") else [v]
|
||||
for _v in new_v:
|
||||
ci_type = CITypeCache.get(_v)
|
||||
if ci_type is not None:
|
||||
self.type_id_list.append(str(ci_type.id))
|
||||
|
||||
if self.type_id_list:
|
||||
type_ids = ",".join(self.type_id_list)
|
||||
_query_sql = QUERY_CI_BY_TYPE.format(type_ids)
|
||||
if self.only_type_query:
|
||||
return _query_sql
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _in_query_handler(attr, v):
|
||||
new_v = v[1:-1].split(";")
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
in_query = " OR {0}.value ".format(table_name).join(['LIKE "{0}"'.format(_v.replace("*", "%")) for _v in new_v])
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, in_query)
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
def _range_query_handler(attr, v):
|
||||
start, end = [x.strip() for x in v[1:-1].split("_TO_")]
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
range_query = "BETWEEN '{0}' AND '{1}'".format(start.replace("*", "%"), end.replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, range_query)
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
def _comparison_query_handler(attr, v):
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
if v.startswith(">=") or v.startswith("<="):
|
||||
comparison_query = "{0} '{1}'".format(v[:2], v[2:].replace("*", "%"))
|
||||
else:
|
||||
comparison_query = "{0} '{1}'".format(v[0], v[1:].replace("*", "%"))
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(table_name, attr.id, comparison_query)
|
||||
return _query_sql
|
||||
|
||||
@staticmethod
|
||||
def __sort_by(field):
|
||||
field = field or ""
|
||||
sort_type = "ASC"
|
||||
if field.startswith("+"):
|
||||
field = field[1:]
|
||||
elif field.startswith("-"):
|
||||
field = field[1:]
|
||||
sort_type = "DESC"
|
||||
return field, sort_type
|
||||
|
||||
def __sort_by_id(self, sort_type, query_sql):
|
||||
ret_sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id FROM ({0}) AS B {1}"
|
||||
|
||||
if self.only_type_query:
|
||||
return ret_sql.format(query_sql, "ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
elif self.type_id_list:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id in ({0}) ".format(
|
||||
",".join(self.type_id_list)))
|
||||
|
||||
return ret_sql.format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id WHERE c_cis.type_id in ({3}) "
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count, ",".join(self.type_id_list)))
|
||||
|
||||
else:
|
||||
self.query_sql = "SELECT B.ci_id FROM ({0}) AS B {1}".format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id ")
|
||||
|
||||
return ret_sql.format(
|
||||
query_sql,
|
||||
"INNER JOIN c_cis on c_cis.id=B.ci_id "
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format((self.page - 1) * self.count, sort_type, self.count))
|
||||
|
||||
def __sort_by_field(self, field, sort_type, query_sql):
|
||||
attr = AttributeCache.get(field)
|
||||
attr_id = attr.id
|
||||
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value
|
||||
FROM ({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name, query_sql, attr_id)
|
||||
new_table = _v_query_sql
|
||||
|
||||
if self.only_type_query or not self.type_id_list:
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id, C.value " \
|
||||
"FROM ({0}) AS C " \
|
||||
"ORDER BY C.value {2} " \
|
||||
"LIMIT {1:d}, {3};".format(new_table, (self.page - 1) * self.count, sort_type, self.count)
|
||||
|
||||
elif self.type_id_list:
|
||||
self.query_sql = """SELECT C.ci_id
|
||||
FROM ({0}) AS C
|
||||
INNER JOIN cis on c_cis.id=C.ci_id
|
||||
WHERE cis.type_id in ({1})""".format(new_table, ",".join(self.type_id_list))
|
||||
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id, C.value
|
||||
FROM ({0}) AS C
|
||||
INNER JOIN cis on c_cis.id=C.ci_id
|
||||
WHERE cis.type_id in ({4})
|
||||
ORDER BY C.value {2}
|
||||
LIMIT {1:d}, {3};""".format(new_table,
|
||||
(self.page - 1) * self.count,
|
||||
sort_type, self.count,
|
||||
",".join(self.type_id_list))
|
||||
|
||||
def _sort_query_handler(self, field, query_sql):
|
||||
|
||||
field, sort_type = self.__sort_by(field)
|
||||
|
||||
if field in ("_id", "ci_id") or not field:
|
||||
return self.__sort_by_id(sort_type, query_sql)
|
||||
else:
|
||||
return self.__sort_by_field(field, sort_type, query_sql)
|
||||
|
||||
@staticmethod
|
||||
def _wrap_sql(operator, alias, _query_sql, query_sql):
|
||||
if operator == "&":
|
||||
query_sql = """SELECT * FROM ({0}) as {1}
|
||||
INNER JOIN ({2}) as {3} USING(ci_id)""".format(query_sql, alias, _query_sql, alias + "A")
|
||||
|
||||
elif operator == "|":
|
||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(query_sql, alias, _query_sql)
|
||||
|
||||
elif operator == "~":
|
||||
query_sql = """SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} USING(ci_id)
|
||||
WHERE {3}.ci_id is NULL""".format(query_sql, alias, _query_sql, alias + "A")
|
||||
|
||||
return query_sql
|
||||
|
||||
def _execute_sql(self, query_sql):
|
||||
v_query_sql = self._sort_query_handler(self.sort, query_sql)
|
||||
|
||||
start = time.time()
|
||||
execute = db.session.execute
|
||||
current_app.logger.debug(v_query_sql)
|
||||
res = execute(v_query_sql).fetchall()
|
||||
end_time = time.time()
|
||||
current_app.logger.debug("query ci ids time is: {0}".format(end_time - start))
|
||||
|
||||
numfound = execute("SELECT FOUND_ROWS();").fetchall()[0][0]
|
||||
current_app.logger.debug("statistics ci ids time is: {0}".format(time.time() - end_time))
|
||||
|
||||
return numfound, res
|
||||
|
||||
def __confirm_type_first(self, queries):
|
||||
for q in queries:
|
||||
if q.startswith("_type"):
|
||||
queries.remove(q)
|
||||
queries.insert(0, q)
|
||||
if len(queries) == 1 or queries[1].startswith("-") or queries[1].startswith("~"):
|
||||
self.only_type_query = True
|
||||
return queries
|
||||
|
||||
def __query_build_by_field(self, queries):
|
||||
query_sql, alias, operator = "", "A", "&"
|
||||
is_first, only_type_query_special = True, True
|
||||
|
||||
for q in queries:
|
||||
_query_sql = ""
|
||||
if ":" in q:
|
||||
k = q.split(":")[0].strip()
|
||||
v = ":".join(q.split(":")[1:]).strip()
|
||||
current_app.logger.debug(v)
|
||||
field, field_type, operator, attr = self._attr_name_proc(k)
|
||||
if field == "_type":
|
||||
_query_sql = self._type_query_handler(v)
|
||||
current_app.logger.debug(_query_sql)
|
||||
elif field == "_id": # exclude all others
|
||||
ci = CI.get_by_id(v)
|
||||
if ci is not None:
|
||||
return 1, [str(v)]
|
||||
elif field:
|
||||
if attr is None:
|
||||
raise SearchError("{0} is not found".format(field))
|
||||
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
_query_sql = self._in_query_handler(attr, v)
|
||||
# range query
|
||||
elif v.startswith("[") and v.endswith("]") and "_TO_" in v:
|
||||
_query_sql = self._range_query_handler(attr, v)
|
||||
# comparison query
|
||||
elif v.startswith(">=") or v.startswith("<=") or v.startswith(">") or v.startswith("<"):
|
||||
_query_sql = self._comparison_query_handler(attr, v)
|
||||
else:
|
||||
table_name = TableMap(attr_name=attr.name).table_name
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.id, 'LIKE "{0}"'.format(v.replace("*", "%")))
|
||||
else:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
elif q:
|
||||
raise SearchError("argument q format invalid: {0}".format(q))
|
||||
|
||||
if is_first and _query_sql and not self.only_type_query:
|
||||
query_sql = "SELECT * FROM ({0}) AS {1}".format(_query_sql, alias)
|
||||
is_first = False
|
||||
alias += "A"
|
||||
elif self.only_type_query and only_type_query_special:
|
||||
is_first = False
|
||||
only_type_query_special = False
|
||||
query_sql = _query_sql
|
||||
elif _query_sql:
|
||||
query_sql = self._wrap_sql(operator, alias, _query_sql, query_sql)
|
||||
alias += "AA"
|
||||
return None, query_sql
|
||||
|
||||
def _query_build_raw(self):
|
||||
|
||||
queries = handle_arg_list(self.orig_query)
|
||||
queries = self.__confirm_type_first(queries)
|
||||
current_app.logger.debug(queries)
|
||||
|
||||
ret, query_sql = self.__query_build_by_field(queries)
|
||||
if ret is not None:
|
||||
return ret, query_sql
|
||||
|
||||
s = time.time()
|
||||
if query_sql:
|
||||
self.query_sql = query_sql
|
||||
current_app.logger.debug(query_sql)
|
||||
numfound, res = self._execute_sql(query_sql)
|
||||
current_app.logger.info("query ci ids is: {0}".format(time.time() - s))
|
||||
return numfound, [_res[0] for _res in res]
|
||||
|
||||
return 0, []
|
||||
|
||||
def _facet_build(self):
|
||||
facet = {}
|
||||
for f in self.facet_field:
|
||||
k, field_type, _, attr = self._attr_name_proc(f)
|
||||
if k:
|
||||
table_name = TableMap(attr_name=k).table_name
|
||||
query_sql = FACET_QUERY.format(table_name, self.query_sql, attr.id)
|
||||
current_app.logger.debug(query_sql)
|
||||
result = db.session.execute(query_sql).fetchall()
|
||||
facet[k] = result
|
||||
|
||||
facet_result = dict()
|
||||
for k, v in facet.items():
|
||||
if not k.startswith('_'):
|
||||
a = getattr(AttributeCache.get(k), self.ret_key)
|
||||
facet_result[a] = [(f[0], f[1], a) for f in v]
|
||||
|
||||
return facet_result
|
||||
|
||||
def _fl_build(self):
|
||||
_fl = list()
|
||||
for f in self.fl:
|
||||
k, _, _, _ = self._attr_name_proc(f)
|
||||
if k:
|
||||
_fl.append(k)
|
||||
|
||||
return _fl
|
||||
|
||||
def search(self):
|
||||
numfound, ci_ids = self._query_build_raw()
|
||||
ci_ids = list(map(str, ci_ids))
|
||||
|
||||
_fl = self._fl_build()
|
||||
|
||||
if self.facet_field and numfound:
|
||||
facet = self._facet_build()
|
||||
else:
|
||||
facet = dict()
|
||||
|
||||
response, counter = [], {}
|
||||
if ci_ids:
|
||||
response = CIManager.get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl)
|
||||
for res in response:
|
||||
ci_type = res.get("ci_type")
|
||||
if ci_type not in counter.keys():
|
||||
counter[ci_type] = 0
|
||||
counter[ci_type] += 1
|
||||
total = len(response)
|
||||
|
||||
return response, counter, total, self.page, numfound, facet
|
|
@ -0,0 +1,138 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import markupsafe
|
||||
|
||||
from flask import abort
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.cmdb.const import type_map
|
||||
from api.lib.cmdb.const import TableMap
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import OperateType
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
|
||||
|
||||
class AttributeValueManager(object):
|
||||
"""
|
||||
manage CI attribute values
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _get_attr(key):
|
||||
"""
|
||||
:param key: id, name or alias
|
||||
:return: attribute instance
|
||||
"""
|
||||
return AttributeCache.get(key)
|
||||
|
||||
def get_attr_values(self, fields, ci_id, ret_key="name", unique_key=None, use_master=False):
|
||||
"""
|
||||
|
||||
:param fields:
|
||||
:param ci_id:
|
||||
:param ret_key: It can be name or alias
|
||||
:param unique_key: primary attribute
|
||||
:param use_master: Only for master-slave read-write separation
|
||||
:return:
|
||||
"""
|
||||
res = dict()
|
||||
for field in fields:
|
||||
attr = self._get_attr(field)
|
||||
if not attr:
|
||||
continue
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
rs = value_table.get_by(ci_id=ci_id,
|
||||
attr_id=attr.id,
|
||||
use_master=use_master,
|
||||
to_dict=False)
|
||||
field_name = getattr(attr, ret_key)
|
||||
|
||||
if attr.is_list:
|
||||
res[field_name] = [type_map["serialize"][attr.value_type](i.value) for i in rs]
|
||||
else:
|
||||
res[field_name] = type_map["serialize"][attr.value_type](rs[0].value) if rs else None
|
||||
|
||||
if unique_key is not None and attr.id == unique_key.id and rs:
|
||||
res['unique'] = unique_key.name
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def __deserialize_value(value_type, value):
|
||||
if not value:
|
||||
return value
|
||||
deserialize = type_map["deserialize"][value_type]
|
||||
try:
|
||||
v = deserialize(value)
|
||||
if isinstance(v, markupsafe.Markup):
|
||||
v = str(v)
|
||||
return v
|
||||
except ValueError:
|
||||
return abort(400, "attribute value <{0}> is invalid".format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_choice(attr_id, value_type, value):
|
||||
choice_values = AttributeManager.get_choice_values(attr_id, value_type)
|
||||
if value not in choice_values:
|
||||
return abort(400, "{0} does not existed in choice values".format(value))
|
||||
|
||||
@staticmethod
|
||||
def __check_is_unique(value_table, attr_id, ci_id, value):
|
||||
db.session.query(value_table.attr_id).filter(
|
||||
value_table.attr_id == attr_id).filter(value_table.deleted.is_(False)).filter(
|
||||
value_table.value == value).filter(value_table.ci_id != ci_id).first() \
|
||||
and abort(400, "attribute <{0}> value {1} must be unique".format(attr_id, value))
|
||||
|
||||
def _validate(self, attr, value, value_table, ci_id):
|
||||
v = self.__deserialize_value(attr.value_type, value)
|
||||
|
||||
attr.is_choice and value and self.__check_is_choice(attr.id, attr.value_type, v)
|
||||
attr.is_unique and self.__check_is_unique(value_table, attr.id, ci_id, v)
|
||||
|
||||
return v
|
||||
|
||||
@staticmethod
|
||||
def _write_change(ci_id, attr_id, operate_type, old, new):
|
||||
AttributeHistoryManger.add(ci_id, [(attr_id, operate_type, old, new)])
|
||||
|
||||
def create_or_update_attr_value(self, key, value, ci_id, _no_attribute_policy=ExistPolicy.IGNORE):
|
||||
"""
|
||||
add or update attribute value, then write history
|
||||
:param key: id, name or alias
|
||||
:param value:
|
||||
:param ci_id:
|
||||
:param _no_attribute_policy: ignore or reject
|
||||
:return:
|
||||
"""
|
||||
attr = self._get_attr(key)
|
||||
if attr is None:
|
||||
if _no_attribute_policy == ExistPolicy.IGNORE:
|
||||
return
|
||||
if _no_attribute_policy == ExistPolicy.REJECT:
|
||||
return abort(400, 'attribute {0} does not exist'.format(key))
|
||||
|
||||
value_table = TableMap(attr_name=attr.name).table
|
||||
existed_attr = value_table.get_by(attr_id=attr.id,
|
||||
ci_id=ci_id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
existed_value = existed_attr and existed_attr.value
|
||||
operate_type = OperateType.ADD if existed_attr is None else OperateType.UPDATE
|
||||
|
||||
value_list = handle_arg_list(value) if attr.is_list else [value]
|
||||
|
||||
for v in value_list:
|
||||
v = self._validate(attr, v, value_table, ci_id)
|
||||
if operate_type == OperateType.ADD:
|
||||
value_table.create(ci_id=ci_id, attr_id=attr.id, value=v)
|
||||
self._write_change(ci_id, attr.id, operate_type, None, v)
|
||||
elif existed_attr.value != v:
|
||||
existed_attr.update(value=v)
|
||||
self._write_change(ci_id, attr.id, operate_type, existed_value, v)
|
|
@ -0,0 +1,121 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.exception import CommitException
|
||||
|
||||
|
||||
class FormatMixin(object):
|
||||
def to_dict(self):
|
||||
return dict([(k.name, getattr(self, k.name)) for k in getattr(self, "__table__").columns])
|
||||
|
||||
@classmethod
|
||||
def get_columns(cls):
|
||||
return {k.name: 1 for k in getattr(cls, "__mapper__").c.values()}
|
||||
|
||||
|
||||
class CRUDMixin(FormatMixin):
|
||||
|
||||
@classmethod
|
||||
def create(cls, flush=False, **kwargs):
|
||||
return cls(**kwargs).save(flush=flush)
|
||||
|
||||
def update(self, flush=False, **kwargs):
|
||||
kwargs.pop("id", None)
|
||||
for attr, value in six.iteritems(kwargs):
|
||||
if value is not None:
|
||||
setattr(self, attr, value)
|
||||
if flush:
|
||||
return self.save(flush=flush)
|
||||
return self.save()
|
||||
|
||||
def save(self, commit=True, flush=False):
|
||||
db.session.add(self)
|
||||
try:
|
||||
if flush:
|
||||
db.session.flush()
|
||||
elif commit:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise CommitException(str(e))
|
||||
|
||||
return self
|
||||
|
||||
def delete(self, flush=False):
|
||||
db.session.delete(self)
|
||||
try:
|
||||
if flush:
|
||||
return db.session.flush()
|
||||
return db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise CommitException(str(e))
|
||||
|
||||
def soft_delete(self, flush=False):
|
||||
setattr(self, "deleted", True)
|
||||
setattr(self, "deleted_at", datetime.datetime.now())
|
||||
self.save(flush=flush)
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, _id):
|
||||
if any((isinstance(_id, six.string_types) and _id.isdigit(),
|
||||
isinstance(_id, (int, float))), ):
|
||||
return getattr(cls, "query").get(int(_id)) or None
|
||||
|
||||
@classmethod
|
||||
def get_by(cls, first=False, to_dict=True, fl=None, exclude=None, deleted=False, use_master=False, **kwargs):
|
||||
db_session = db.session if not use_master else db.session().using_bind("master")
|
||||
fl = fl.strip().split(",") if fl and isinstance(fl, six.string_types) else (fl or [])
|
||||
exclude = exclude.strip().split(",") if exclude and isinstance(exclude, six.string_types) else (exclude or [])
|
||||
|
||||
keys = cls.get_columns()
|
||||
fl = [k for k in fl if k in keys]
|
||||
fl = [k for k in keys if k not in exclude and not k.isupper()] if exclude else fl
|
||||
fl = list(filter(lambda x: "." not in x, fl))
|
||||
|
||||
if hasattr(cls, "deleted") and deleted is not None:
|
||||
kwargs["deleted"] = deleted
|
||||
|
||||
if fl:
|
||||
query = db_session.query(*[getattr(cls, k) for k in fl])
|
||||
query = query.filter_by(**kwargs)
|
||||
result = [{k: getattr(i, k) for k in fl} for i in query]
|
||||
else:
|
||||
result = [i.to_dict() if to_dict else i for i in getattr(cls, 'query').filter_by(**kwargs)]
|
||||
|
||||
return result[0] if first and result else (None if first else result)
|
||||
|
||||
@classmethod
|
||||
def get_by_like(cls, to_dict=True, **kwargs):
|
||||
query = db.session.query(cls)
|
||||
for k, v in kwargs.items():
|
||||
query = query.filter(getattr(cls, k).ilike('%{0}%'.format(v)))
|
||||
return [i.to_dict() if to_dict else i for i in query]
|
||||
|
||||
|
||||
class SoftDeleteMixin(object):
|
||||
deleted_at = db.Column(db.DateTime)
|
||||
deleted = db.Column(db.Boolean, index=True, default=False)
|
||||
|
||||
|
||||
class TimestampMixin(object):
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
|
||||
updated_at = db.Column(db.DateTime, onupdate=lambda: datetime.datetime.now())
|
||||
|
||||
|
||||
class SurrogatePK(object):
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
|
||||
class Model(SoftDeleteMixin, TimestampMixin, CRUDMixin, db.Model, SurrogatePK):
|
||||
__abstract__ = True
|
||||
|
||||
|
||||
class CRUDModel(db.Model, CRUDMixin):
|
||||
__abstract__ = True
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
|
||||
def kwargs_required(*required_args):
|
||||
def decorate(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
for arg in required_args:
|
||||
if arg not in kwargs:
|
||||
return abort(400, "Argument <{0}> is required".format(arg))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorate
|
||||
|
||||
|
||||
def args_required(*required_args):
|
||||
def decorate(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
for arg in required_args:
|
||||
if arg not in request.values:
|
||||
return abort(400, "Argument <{0}> is required".format(arg))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorate
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
class CommitException(Exception):
|
||||
pass
|
|
@ -0,0 +1,48 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import hashlib
|
||||
|
||||
import requests
|
||||
from future.moves.urllib.parse import urlparse
|
||||
from flask import abort
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def build_api_key(path, params):
|
||||
g.user is not None or abort(403, u"您得登陆才能进行该操作")
|
||||
key = g.user.key
|
||||
secret = g.user.secret
|
||||
values = "".join([str(params[k]) for k in sorted(params.keys())
|
||||
if params[k] is not None]) if params.keys() else ""
|
||||
_secret = "".join([path, secret, values]).encode("utf-8")
|
||||
params["_secret"] = hashlib.sha1(_secret).hexdigest()
|
||||
params["_key"] = key
|
||||
return params
|
||||
|
||||
|
||||
def api_request(url, method="get", params=None, ret_key=None):
|
||||
params = params or {}
|
||||
resp = None
|
||||
try:
|
||||
method = method.lower()
|
||||
params = build_api_key(urlparse(url).path, params)
|
||||
if method == "get":
|
||||
resp = getattr(requests, method)(url, params=params)
|
||||
else:
|
||||
resp = getattr(requests, method)(url, data=params)
|
||||
if resp.status_code != 200:
|
||||
return abort(resp.status_code, resp.json().get("message"))
|
||||
resp = resp.json()
|
||||
if ret_key is not None:
|
||||
return resp.get(ret_key)
|
||||
return resp
|
||||
except Exception as e:
|
||||
code = e.code if hasattr(e, "code") else None
|
||||
if isinstance(code, int) and resp is not None:
|
||||
return abort(code, resp.json().get("message"))
|
||||
current_app.logger.warning(url)
|
||||
current_app.logger.warning(params)
|
||||
current_app.logger.error(str(e))
|
||||
return abort(500, "server unknown error")
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.header import Header
|
||||
from email.mime.image import MIMEImage
|
||||
import smtplib
|
||||
import time
|
||||
from email import Utils
|
||||
|
||||
|
||||
def send_mail(sender, receiver, subject, content, ctype="html", pics=()):
|
||||
"""subject and body are unicode objects"""
|
||||
if not sender:
|
||||
sender = current_app.config.get("DEFAULT_MAIL_SENDER")
|
||||
smtpserver = current_app.config.get("MAIL_SERVER")
|
||||
if ctype == "html":
|
||||
msg = MIMEText(content, 'html', 'utf-8')
|
||||
else:
|
||||
msg = MIMEText(content, 'plain', 'utf-8')
|
||||
|
||||
if len(pics) != 0:
|
||||
msgRoot = MIMEMultipart('related')
|
||||
msgText = MIMEText(content, 'html', 'utf-8')
|
||||
msgRoot.attach(msgText)
|
||||
i = 1
|
||||
for pic in pics:
|
||||
fp = open(pic, "rb")
|
||||
image = MIMEImage(fp.read())
|
||||
fp.close()
|
||||
image.add_header('Content-ID', '<img%02d>' % i)
|
||||
msgRoot.attach(image)
|
||||
i += 1
|
||||
msg = msgRoot
|
||||
|
||||
msg['Subject'] = Header(subject, 'utf-8')
|
||||
msg['From'] = sender
|
||||
msg['To'] = ';'.join(receiver)
|
||||
msg['Message-ID'] = Utils.make_msgid()
|
||||
msg['date'] = time.strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||
|
||||
smtp = smtplib.SMTP()
|
||||
smtp.connect(smtpserver, 25)
|
||||
# smtp.login(username, password)
|
||||
smtp.sendmail(sender, receiver, msg.as_string())
|
||||
smtp.quit()
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -0,0 +1,139 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import functools
|
||||
|
||||
import six
|
||||
|
||||
from flask import current_app, g, request
|
||||
from flask import session, abort
|
||||
|
||||
from api.extensions import cache
|
||||
|
||||
|
||||
def get_access_token():
|
||||
return
|
||||
|
||||
|
||||
class AccessTokenCache(object):
|
||||
@classmethod
|
||||
def get(cls):
|
||||
if cache.get("AccessToken") is not None:
|
||||
return cache.get("AccessToken")
|
||||
|
||||
res = get_access_token() or ""
|
||||
|
||||
cache.set("AccessToken", res, timeout=60 * 60)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def clean(cls):
|
||||
cache.clear("AccessToken")
|
||||
|
||||
|
||||
class ACLManager(object):
|
||||
def __init__(self):
|
||||
self.access_token = AccessTokenCache.get()
|
||||
self.acl_session = dict(uid=session.get("uid"),
|
||||
token=self.access_token)
|
||||
|
||||
self.user_info = session["acl"] if "acl" in session else {}
|
||||
|
||||
def add_resource(self, name, resource_type_name=None):
|
||||
pass
|
||||
|
||||
def grant_resource_to_role(self, name, role, resource_type_name=None):
|
||||
pass
|
||||
|
||||
def del_resource(self, name, resource_type_name=None):
|
||||
pass
|
||||
|
||||
def get_user_info(self, username):
|
||||
return dict()
|
||||
|
||||
def get_resources(self, resource_type_name=None):
|
||||
if "acl" not in session:
|
||||
abort(405)
|
||||
return []
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm):
|
||||
if "acl" not in session:
|
||||
abort(405)
|
||||
return True
|
||||
|
||||
|
||||
def validate_permission(resources, resource_type, perm):
|
||||
if not resources:
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if g.user.username == "worker":
|
||||
return
|
||||
|
||||
resources = [resources] if isinstance(resources, six.string_types) else resources
|
||||
for resource in resources:
|
||||
if not ACLManager().has_permission(resource, resource_type, perm):
|
||||
return abort(403, "has no permission")
|
||||
|
||||
|
||||
def can_access_resources(resource_type):
|
||||
def decorator_can_access_resources(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_can_access_resources(*args, **kwargs):
|
||||
if current_app.config.get("USE_ACL"):
|
||||
res = ACLManager().get_resources(resource_type)
|
||||
result = {i.get("name"): i.get("permissions") for i in res}
|
||||
if hasattr(g, "resources"):
|
||||
g.resources.update({resource_type: result})
|
||||
else:
|
||||
g.resources = {resource_type: result}
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_can_access_resources
|
||||
return decorator_can_access_resources
|
||||
|
||||
|
||||
def has_perm(resources, resource_type, perm):
|
||||
def decorator_has_perm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_has_perm(*args, **kwargs):
|
||||
if not resources:
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
validate_permission(resources, resource_type, perm)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_has_perm
|
||||
return decorator_has_perm
|
||||
|
||||
|
||||
def has_perm_from_args(arg_name, resource_type, perm, callback=None):
|
||||
def decorator_has_perm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_has_perm(*args, **kwargs):
|
||||
if not arg_name:
|
||||
return
|
||||
resource = request.view_args.get(arg_name) or request.values.get(arg_name)
|
||||
if callback is not None and resource:
|
||||
resource = callback(resource)
|
||||
|
||||
if current_app.config.get("USE_ACL") and resource:
|
||||
validate_permission(resource, resource_type, perm)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_has_perm
|
||||
return decorator_has_perm
|
||||
|
||||
|
||||
def role_required(role_name):
|
||||
def decorator_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_role_required(*args, **kwargs):
|
||||
if not role_name:
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []):
|
||||
return abort(403, "Role {0} is required".format(role_name))
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_role_required
|
||||
return decorator_role_required
|
|
@ -0,0 +1,102 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import jwt
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import g
|
||||
from flask import abort
|
||||
from flask_login import login_user
|
||||
|
||||
from api.models.account import User
|
||||
from api.models.account import UserCache
|
||||
|
||||
|
||||
def _auth_with_key():
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
path = request.path
|
||||
keys = sorted(request.values.keys())
|
||||
req_args = [request.values[k] for k in keys if str(k) not in ("_key", "_secret")]
|
||||
user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path)
|
||||
if user and authenticated:
|
||||
login_user(user)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_session():
|
||||
if isinstance(getattr(g, 'user', None), User):
|
||||
login_user(g.user)
|
||||
return True
|
||||
if "acl" in session and "userName" in (session["acl"] or {}):
|
||||
login_user(UserCache.get(session["acl"]["userName"]))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_token():
|
||||
auth_headers = request.headers.get('Access-Token', '').strip()
|
||||
if not auth_headers:
|
||||
return False
|
||||
|
||||
try:
|
||||
token = auth_headers
|
||||
data = jwt.decode(token, current_app.config['SECRET_KEY'])
|
||||
user = User.query.filter_by(email=data['sub']).first()
|
||||
if not user:
|
||||
return False
|
||||
|
||||
login_user(user)
|
||||
return True
|
||||
except jwt.ExpiredSignatureError:
|
||||
return False
|
||||
except (jwt.InvalidTokenError, Exception) as e:
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_ip_white_list():
|
||||
ip = request.remote_addr
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
|
||||
if not key and not secret and ip.strip() in current_app.config.get("WHITE_LIST", []): # TODO
|
||||
user = UserCache.get("worker")
|
||||
login_user(user)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def auth_required(func):
|
||||
if request.json is not None:
|
||||
setattr(request, 'values', request.json)
|
||||
else:
|
||||
setattr(request, 'values', request.values.to_dict())
|
||||
|
||||
current_app.logger.debug(request.values)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
if not getattr(func, 'authenticated', True):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if _auth_with_session() or _auth_with_key() or _auth_with_token() or _auth_with_ip_white_list():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
abort(401)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def auth_abandoned(func):
|
||||
setattr(func, "authenticated", False)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
|
@ -1,14 +1,38 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import six
|
||||
import redis
|
||||
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def get_page(page):
|
||||
try:
|
||||
page = int(page)
|
||||
except ValueError:
|
||||
page = 1
|
||||
return page if page >= 1 else 1
|
||||
|
||||
|
||||
def get_page_size(page_size):
|
||||
if page_size == "all":
|
||||
return page_size
|
||||
|
||||
try:
|
||||
page_size = int(page_size)
|
||||
except (ValueError, TypeError):
|
||||
page_size = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
return page_size if page_size >= 1 else current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
|
||||
|
||||
def handle_arg_list(arg):
|
||||
return list(filter(lambda x: x != "", arg.strip().split(","))) if isinstance(arg, six.string_types) else arg
|
||||
|
||||
|
||||
class RedisHandler(object):
|
||||
def __init__(self, flask_app=None):
|
||||
def __init__(self, flask_app=None, prefix=None):
|
||||
self.flask_app = flask_app
|
||||
self.prefix = prefix
|
||||
self.r = None
|
||||
|
||||
def init_app(self, app):
|
||||
self.flask_app = app
|
||||
|
@ -21,55 +45,30 @@ class RedisHandler(object):
|
|||
db=config.get("REDIS_DB"))
|
||||
self.r = redis.Redis(connection_pool=pool)
|
||||
except Exception as e:
|
||||
current_app.logger.warning(str(e))
|
||||
current_app.logger.error("init redis connection failed")
|
||||
|
||||
# @classmethod
|
||||
# def instance(cls):
|
||||
# if not hasattr(cls, "_instance"):
|
||||
# cls._instance = cls()
|
||||
# return cls._instance
|
||||
|
||||
def get(self, ci_ids, key="CMDB_CI"):
|
||||
def get(self, key_ids):
|
||||
try:
|
||||
value = self.r.hmget(key, ci_ids)
|
||||
value = self.r.hmget(self.prefix, key_ids)
|
||||
except Exception as e:
|
||||
current_app.logger.error("get redis error, %s" % str(e))
|
||||
return
|
||||
return value
|
||||
|
||||
def _set(self, ci, key="CMDB_CI"):
|
||||
def _set(self, obj):
|
||||
try:
|
||||
self.r.hmset(key, ci)
|
||||
self.r.hmset(self.prefix, obj)
|
||||
except Exception as e:
|
||||
current_app.logger.error("set redis error, %s" % str(e))
|
||||
|
||||
def add(self, ci):
|
||||
self._set(ci)
|
||||
def add(self, obj):
|
||||
self._set(obj)
|
||||
|
||||
def delete(self, ci_id, key="CMDB_CI"):
|
||||
def delete(self, key_id):
|
||||
try:
|
||||
ret = self.r.hdel(key, ci_id)
|
||||
ret = self.r.hdel(self.prefix, key_id)
|
||||
if not ret:
|
||||
current_app.logger.warn("ci [%d] is not in redis" % ci_id)
|
||||
current_app.logger.warn("[%d] is not in redis" % key_id)
|
||||
except Exception as e:
|
||||
current_app.logger.error("delete redis key error, %s" % str(e))
|
||||
|
||||
|
||||
def get_page(page):
|
||||
try:
|
||||
page = int(page)
|
||||
except ValueError:
|
||||
page = 1
|
||||
if page < 1:
|
||||
page = 1
|
||||
return page
|
||||
|
||||
|
||||
def get_per_page(per_page):
|
||||
try:
|
||||
per_page = int(per_page)
|
||||
except:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
if per_page < 1:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
return per_page
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from .account import User
|
||||
from .cmdb import *
|
|
@ -1,51 +1,24 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import hashlib
|
||||
import copy
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
from werkzeug.utils import cached_property
|
||||
from flask.ext.sqlalchemy import BaseQuery
|
||||
from flask.ext.principal import RoleNeed
|
||||
from flask.ext.principal import UserNeed
|
||||
from flask.ext.principal import Permission
|
||||
import six
|
||||
from flask import current_app
|
||||
from flask_sqlalchemy import BaseQuery
|
||||
|
||||
from extensions import db
|
||||
from extensions import cache
|
||||
from permissions import admin
|
||||
from models import row2dict
|
||||
from api.extensions import db
|
||||
from api.extensions import cache
|
||||
from api.lib.database import CRUDModel
|
||||
|
||||
|
||||
class UserQuery(BaseQuery):
|
||||
def from_identity(self, identity):
|
||||
"""
|
||||
Loads user from flask.ext.principal.Identity instance and
|
||||
assigns permissions from user.
|
||||
|
||||
A "user" instance is monkey patched to the identity instance.
|
||||
|
||||
If no user found then None is returned.
|
||||
"""
|
||||
|
||||
try:
|
||||
_id = identity.id
|
||||
if _id:
|
||||
_id = int(_id)
|
||||
user = self.get(_id)
|
||||
except ValueError:
|
||||
user = None
|
||||
except Exception:
|
||||
user = None
|
||||
if user:
|
||||
identity.provides.update(user.provides)
|
||||
identity.user = user
|
||||
return user
|
||||
|
||||
def authenticate(self, login, password):
|
||||
user = self.filter(db.or_(User.username == login,
|
||||
User.email == login)).first()
|
||||
if user:
|
||||
current_app.logger.info(user)
|
||||
authenticated = user.check_password(password)
|
||||
else:
|
||||
authenticated = False
|
||||
|
@ -60,7 +33,7 @@ class UserQuery(BaseQuery):
|
|||
authenticated = True
|
||||
else:
|
||||
authenticated = False
|
||||
return row2dict(user), authenticated
|
||||
return user, authenticated
|
||||
|
||||
def search(self, key):
|
||||
query = self.filter(db.or_(User.email == key,
|
||||
|
@ -80,16 +53,14 @@ class UserQuery(BaseQuery):
|
|||
user = self.filter(User.uid == uid).first()
|
||||
return copy.deepcopy(user)
|
||||
|
||||
def is_exits(self, username):
|
||||
user = self.filter(User.username == username).first()
|
||||
return user is not None
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
class User(CRUDModel):
|
||||
__tablename__ = 'users'
|
||||
__bind_key__ = "user"
|
||||
query_class = UserQuery
|
||||
|
||||
ADMIN = 1
|
||||
OP = 2
|
||||
|
||||
uid = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = db.Column(db.String(32), unique=True)
|
||||
|
@ -98,21 +69,15 @@ class User(db.Model):
|
|||
catalog = db.Column(db.String(64))
|
||||
email = db.Column(db.String(100), unique=True, nullable=False)
|
||||
mobile = db.Column(db.String(14), unique=True)
|
||||
_password = db.Column("password", db.String(80), nullable=False)
|
||||
_password = db.Column("password", db.String(80))
|
||||
key = db.Column(db.String(32), nullable=False)
|
||||
secret = db.Column(db.String(32), nullable=False)
|
||||
date_joined = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
last_login = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
block = db.Column(db.Boolean, default=False)
|
||||
has_logined = db.Column(db.Boolean, default=False)
|
||||
|
||||
class Permissions(object):
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
@cached_property
|
||||
def is_admin(self):
|
||||
return Permission(UserNeed(self.obj.id)) & admin
|
||||
wx_id = db.Column(db.String(32))
|
||||
avatar = db.Column(db.String(128))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(User, self).__init__(*args, **kwargs)
|
||||
|
@ -120,31 +85,31 @@ class User(db.Model):
|
|||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
@cached_property
|
||||
def permissions(self):
|
||||
return self.Permissions(self)
|
||||
def is_active(self):
|
||||
return not self.block
|
||||
|
||||
def get_id(self):
|
||||
return self.uid
|
||||
|
||||
@staticmethod
|
||||
def is_authenticated():
|
||||
return True
|
||||
|
||||
def _get_password(self):
|
||||
return self._password
|
||||
|
||||
def _set_password(self, password):
|
||||
self._password = password
|
||||
self._password = hashlib.md5(password).hexdigest()
|
||||
|
||||
password = db.synonym("_password", descriptor=property(
|
||||
_get_password, _set_password))
|
||||
password = db.synonym("_password",
|
||||
descriptor=property(_get_password,
|
||||
_set_password))
|
||||
|
||||
def check_password(self, password):
|
||||
if self.password is None:
|
||||
return False
|
||||
return self.password == password
|
||||
|
||||
@cached_property
|
||||
def provides(self):
|
||||
needs = [RoleNeed('authenticated'), UserNeed(self.uid)]
|
||||
for r in self.rolenames:
|
||||
needs.append(RoleNeed(r))
|
||||
if self.is_admin:
|
||||
needs.append(RoleNeed('admin'))
|
||||
return needs
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
urs = db.session.query(UserRole.rid).filter(
|
||||
|
@ -153,23 +118,28 @@ class User(db.Model):
|
|||
|
||||
@property
|
||||
def rolenames(self):
|
||||
return [db.session.query(Role.role_name).filter(
|
||||
Role.rid == rid).first().role_name for rid in self.roles]
|
||||
roles = list()
|
||||
for rid in self.roles:
|
||||
role = db.session.query(Role).filter(Role.rid == rid).first()
|
||||
roles.append(role.role_name)
|
||||
return roles
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return self.ADMIN in self.roles
|
||||
|
||||
|
||||
class Role(db.Model):
|
||||
class Role(CRUDModel):
|
||||
__tablename__ = 'roles'
|
||||
__bind_key__ = "user"
|
||||
|
||||
rid = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
role_name = db.Column(db.String(64), nullable=False, unique=True)
|
||||
|
||||
|
||||
class UserRole(db.Model):
|
||||
class UserRole(CRUDModel):
|
||||
__tablename__ = 'users_roles'
|
||||
__bind_key__ = "user"
|
||||
|
||||
uid = db.Column(db.Integer, db.ForeignKey('users.uid'), primary_key=True)
|
||||
rid = db.Column(db.Integer, db.ForeignKey('roles.rid'), primary_key=True)
|
||||
|
@ -211,7 +181,7 @@ class RoleCache(object):
|
|||
if not role:
|
||||
role = db.session.query(Role).filter(Role.rid == rid).first()
|
||||
cls.set(role)
|
||||
elif isinstance(rid, basestring):
|
||||
elif isinstance(rid, six.string_types):
|
||||
role = cache.get("Role::role_name::%s" % rid)
|
||||
if not role:
|
||||
role = db.session.query(Role).filter(
|
|
@ -0,0 +1,325 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from api.lib.database import Model
|
||||
from api.extensions import db
|
||||
|
||||
|
||||
# template
|
||||
|
||||
class RelationType(Model):
|
||||
__tablename__ = "c_relation_types"
|
||||
|
||||
name = db.Column(db.String(16), index=True)
|
||||
|
||||
|
||||
class CITypeGroup(Model):
|
||||
__tablename__ = "c_ci_type_groups"
|
||||
|
||||
name = db.Column(db.String(32))
|
||||
|
||||
|
||||
class CITypeGroupItem(Model):
|
||||
__tablename__ = "c_ci_type_group_items"
|
||||
|
||||
group_id = db.Column(db.Integer, db.ForeignKey("c_ci_type_groups.id"), nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
|
||||
|
||||
class CIType(Model):
|
||||
__tablename__ = "c_ci_types"
|
||||
|
||||
name = db.Column(db.String(32))
|
||||
alias = db.Column(db.String(32))
|
||||
unique_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||
icon_url = db.Column(db.String(256))
|
||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||
|
||||
unique_key = db.relationship("Attribute", backref="c_ci_types.unique_id")
|
||||
|
||||
|
||||
class CITypeRelation(Model):
|
||||
__tablename__ = "c_ci_type_relations"
|
||||
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
|
||||
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
|
||||
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_type_relations.relation_type_id")
|
||||
|
||||
|
||||
class Attribute(Model):
|
||||
__tablename__ = "c_attributes"
|
||||
|
||||
INT = "0"
|
||||
FLOAT = "1"
|
||||
TEXT = "2"
|
||||
DATETIME = "3"
|
||||
DATE = "4"
|
||||
TIME = "5"
|
||||
|
||||
name = db.Column(db.String(32), nullable=False)
|
||||
alias = db.Column(db.String(32), nullable=False)
|
||||
value_type = db.Column(db.Enum(INT, FLOAT, TEXT, DATETIME, DATE, TIME), default=TEXT, nullable=False)
|
||||
|
||||
is_choice = db.Column(db.Boolean, default=False)
|
||||
is_list = db.Column(db.Boolean, default=False)
|
||||
is_unique = db.Column(db.Boolean, default=False)
|
||||
is_index = db.Column(db.Boolean, default=False)
|
||||
is_link = db.Column(db.Boolean, default=False)
|
||||
is_password = db.Column(db.Boolean, default=False)
|
||||
is_sortable = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
||||
class CITypeAttribute(Model):
|
||||
__tablename__ = "c_ci_type_attributes"
|
||||
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
is_required = db.Column(db.Boolean, default=False)
|
||||
default_show = db.Column(db.Boolean, default=True)
|
||||
|
||||
attr = db.relationship("Attribute", backref="c_ci_type_attributes.attr_id")
|
||||
|
||||
|
||||
class CITypeAttributeGroup(Model):
|
||||
__tablename__ = "c_ci_type_attribute_groups"
|
||||
|
||||
name = db.Column(db.String(64))
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
|
||||
|
||||
class CITypeAttributeGroupItem(Model):
|
||||
__tablename__ = "c_ci_type_attribute_group_items"
|
||||
|
||||
group_id = db.Column(db.Integer, db.ForeignKey("c_ci_type_attribute_groups.id"), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"), nullable=False)
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
|
||||
|
||||
# instance
|
||||
|
||||
class CI(Model):
|
||||
__tablename__ = "c_cis"
|
||||
|
||||
REVIEW = "0"
|
||||
VALIDATE = "1"
|
||||
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
status = db.Column(db.Enum(REVIEW, VALIDATE, name="status"))
|
||||
heartbeat = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
|
||||
|
||||
ci_type = db.relationship("CIType", backref="c_cis.type_id")
|
||||
|
||||
|
||||
class CIRelation(Model):
|
||||
__tablename__ = "c_ci_relations"
|
||||
|
||||
first_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False)
|
||||
second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False)
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
|
||||
more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
|
||||
|
||||
first_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.first_ci_id")
|
||||
second_ci = db.relationship("CI", primaryjoin="CI.id==CIRelation.second_ci_id")
|
||||
relation_type = db.relationship("RelationType", backref="c_ci_relations.relation_type_id")
|
||||
|
||||
|
||||
class IntegerChoice(Model):
|
||||
__tablename__ = 'c_choice_integers'
|
||||
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
attr = db.relationship("Attribute", backref="c_choice_integers.attr_id")
|
||||
|
||||
|
||||
class FloatChoice(Model):
|
||||
__tablename__ = 'c_choice_floats'
|
||||
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
attr = db.relationship("Attribute", backref="c_choice_floats.attr_id")
|
||||
|
||||
|
||||
class TextChoice(Model):
|
||||
__tablename__ = 'c_choice_texts'
|
||||
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
|
||||
attr = db.relationship("Attribute", backref="c_choice_texts.attr_id")
|
||||
|
||||
|
||||
class CIIndexValueInteger(Model):
|
||||
__tablename__ = "c_value_index_integers"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_index_integers.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_index_integers.attr_id")
|
||||
|
||||
__table_args__ = (db.Index("integer_attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueFloat(Model):
|
||||
__tablename__ = "c_value_index_floats"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_index_floats.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_index_floats.attr_id")
|
||||
|
||||
__table_args__ = (db.Index("float_attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueText(Model):
|
||||
__tablename__ = "c_value_index_texts"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.String(128), nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_index_texts.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_index_texts.attr_id")
|
||||
|
||||
__table_args__ = (db.Index("text_attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueDateTime(Model):
|
||||
__tablename__ = "c_value_index_datetime"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.DateTime, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_index_datetime.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_index_datetime.attr_id")
|
||||
|
||||
__table_args__ = (db.Index("datetime_attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIValueInteger(Model):
|
||||
__tablename__ = "c_value_integers"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_integers.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_integers.attr_id")
|
||||
|
||||
|
||||
class CIValueFloat(Model):
|
||||
__tablename__ = "c_value_floats"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_floats.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_floats.attr_id")
|
||||
|
||||
|
||||
class CIValueText(Model):
|
||||
__tablename__ = "c_value_texts"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_texts.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_texts.attr_id")
|
||||
|
||||
|
||||
class CIValueDateTime(Model):
|
||||
__tablename__ = "c_value_datetime"
|
||||
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('c_cis.id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey('c_attributes.id'), nullable=False)
|
||||
value = db.Column(db.DateTime, nullable=False)
|
||||
|
||||
ci = db.relationship("CI", backref="c_value_datetime.ci_id")
|
||||
attr = db.relationship("Attribute", backref="c_value_datetime.attr_id")
|
||||
|
||||
|
||||
# history
|
||||
class OperationRecord(Model):
|
||||
__tablename__ = "c_records"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
origin = db.Column(db.String(32))
|
||||
ticket_id = db.Column(db.String(32))
|
||||
reason = db.Column(db.Text)
|
||||
|
||||
|
||||
class AttributeHistory(Model):
|
||||
__tablename__ = "c_attribute_histories"
|
||||
|
||||
ADD = "0"
|
||||
DELETE = "1"
|
||||
UPDATE = "2"
|
||||
|
||||
operate_type = db.Column(db.Enum(ADD, DELETE, UPDATE, name="operate_type"))
|
||||
record_id = db.Column(db.Integer, db.ForeignKey("c_records.id"), nullable=False)
|
||||
ci_id = db.Column(db.Integer, index=True, nullable=False)
|
||||
attr_id = db.Column(db.Integer, index=True)
|
||||
old = db.Column(db.Text)
|
||||
new = db.Column(db.Text)
|
||||
|
||||
|
||||
class CIRelationHistory(Model):
|
||||
__tablename__ = "c_relation_histories"
|
||||
|
||||
ADD = "0"
|
||||
DELETE = "1"
|
||||
|
||||
operate_type = db.Column(db.Enum(ADD, DELETE, name="operate_type"))
|
||||
record_id = db.Column(db.Integer, db.ForeignKey("c_records.id"), nullable=False)
|
||||
first_ci_id = db.Column(db.Integer)
|
||||
second_ci_id = db.Column(db.Integer)
|
||||
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"))
|
||||
relation_id = db.Column(db.Integer, nullable=False)
|
||||
|
||||
|
||||
# preference
|
||||
class PreferenceShowAttributes(Model):
|
||||
__tablename__ = "c_preference_show_attributes"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
|
||||
order = db.Column(db.SmallInteger, default=0)
|
||||
|
||||
ci_type = db.relationship("CIType", backref="c_preference_show_attributes.type_id")
|
||||
attr = db.relationship("Attribute", backref="c_preference_show_attributes.attr_id")
|
||||
|
||||
|
||||
class PreferenceTreeView(Model):
|
||||
__tablename__ = "c_preference_tree_views"
|
||||
|
||||
uid = db.Column(db.Integer, index=True, nullable=False)
|
||||
type_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
levels = db.Column(db.Text) # TODO: JSON
|
||||
|
||||
|
||||
class PreferenceRelationView(Model):
|
||||
__tablename__ = "c_preference_relation_views"
|
||||
|
||||
name = db.Column(db.String(8), index=True, nullable=False)
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
||||
child_id = db.Column(db.Integer, db.ForeignKey("c_ci_types.id"), nullable=False)
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from inspect import getmembers, isclass
|
||||
|
||||
import six
|
||||
from flask import jsonify
|
||||
from flask_restful import Resource
|
||||
|
||||
from api.lib.perm.auth import auth_required
|
||||
|
||||
|
||||
class APIView(Resource):
|
||||
method_decorators = [auth_required]
|
||||
|
||||
def __init__(self):
|
||||
super(APIView, self).__init__()
|
||||
|
||||
@staticmethod
|
||||
def jsonify(*args, **kwargs):
|
||||
return jsonify(*args, **kwargs)
|
||||
|
||||
|
||||
API_PACKAGE = "api"
|
||||
|
||||
|
||||
def register_resources(resource_path, rest_api):
|
||||
for root, _, files in os.walk(os.path.join(resource_path)):
|
||||
for filename in files:
|
||||
if not filename.startswith("_") and filename.endswith("py"):
|
||||
module_path = os.path.join(API_PACKAGE, root[root.index("views"):])
|
||||
if module_path not in sys.path:
|
||||
sys.path.insert(1, module_path)
|
||||
view = __import__(os.path.splitext(filename)[0])
|
||||
resource_list = [o[0] for o in getmembers(view) if isclass(o[1]) and issubclass(o[1], Resource)]
|
||||
resource_list = [i for i in resource_list if i != "APIView"]
|
||||
for resource_cls_name in resource_list:
|
||||
resource_cls = getattr(view, resource_cls_name)
|
||||
if not hasattr(resource_cls, "url_prefix"):
|
||||
resource_cls.url_prefix = ("",)
|
||||
if isinstance(resource_cls.url_prefix, six.string_types):
|
||||
resource_cls.url_prefix = (resource_cls.url_prefix,)
|
||||
|
||||
rest_api.add_resource(resource_cls, *resource_cls.url_prefix)
|
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Application configuration.
|
||||
|
||||
Most configuration is set via environment variables.
|
||||
|
||||
For local development, use a .env file to set
|
||||
environment variables.
|
||||
"""
|
||||
from environs import Env
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
ENV = env.str("FLASK_ENV", default="production")
|
||||
DEBUG = ENV == "development"
|
||||
SECRET_KEY = env.str("SECRET_KEY")
|
||||
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
|
||||
DEBUG_TB_ENABLED = DEBUG
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
|
||||
|
||||
ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
|
||||
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
SQLALCHEMY_BINDS = {
|
||||
"user": 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
|
||||
}
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
'pool_recycle': 300,
|
||||
}
|
||||
|
||||
# # cache
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_KEY_PREFIX = "CMDB::"
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
# # log
|
||||
LOG_PATH = './logs/app.log'
|
||||
LOG_LEVEL = 'DEBUG'
|
||||
|
||||
|
||||
# # mail
|
||||
MAIL_SERVER = ''
|
||||
MAIL_PORT = 25
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USE_SSL = False
|
||||
MAIL_DEBUG = True
|
||||
MAIL_USERNAME = ''
|
||||
MAIL_PASSWORD = ''
|
||||
DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# # queue
|
||||
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/2"
|
||||
BROKER_URL = 'redis://127.0.0.1:6379/2'
|
||||
BROKER_VHOST = '/'
|
||||
|
||||
|
||||
# # SSO
|
||||
CAS_SERVER = "http://sso.xxx.com"
|
||||
CAS_VALIDATE_SERVER = "http://sso.xxx.com"
|
||||
CAS_LOGIN_ROUTE = "/cas/login"
|
||||
CAS_LOGOUT_ROUTE = "/cas/logout"
|
||||
CAS_VALIDATE_ROUTE = "/cas/serviceValidate"
|
||||
CAS_AFTER_LOGIN = "/"
|
||||
DEFAULT_SERVICE = "http://127.0.0.1:8000"
|
||||
|
||||
# # pagination
|
||||
DEFAULT_PAGE_COUNT = 50
|
||||
|
||||
# # permission
|
||||
WHITE_LIST = ["127.0.0.1"]
|
||||
USE_ACL = False
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -6,24 +6,27 @@ import time
|
|||
|
||||
from flask import current_app
|
||||
|
||||
from extensions import celery
|
||||
from extensions import db
|
||||
from extensions import rd
|
||||
import lib.ci
|
||||
import api.lib.cmdb.ci
|
||||
from api.extensions import celery
|
||||
from api.extensions import db
|
||||
from api.extensions import rd
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_cache", queue="cmdb_async")
|
||||
@celery.task(name="cmdb.ci_cache", queue=CMDB_QUEUE)
|
||||
def ci_cache(ci_id):
|
||||
time.sleep(0.1)
|
||||
db.session.close()
|
||||
m = lib.ci.CIManager()
|
||||
ci = m.get_ci_by_id(ci_id, need_children=False, use_master=True)
|
||||
|
||||
m = api.lib.cmdb.ci.CIManager()
|
||||
ci = m.get_ci_by_id_from_db(ci_id, need_children=False, use_master=False)
|
||||
rd.delete(ci_id)
|
||||
rd.add({ci_id: json.dumps(ci)})
|
||||
|
||||
current_app.logger.info("%d caching.........." % ci_id)
|
||||
|
||||
|
||||
@celery.task(name="cmdb.ci_delete", queue="cmdb_async")
|
||||
@celery.task(name="cmdb.ci_delete", queue=CMDB_QUEUE)
|
||||
def ci_delete(ci_id):
|
||||
current_app.logger.info(ci_id)
|
||||
rd.delete(ci_id)
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.extensions import celery
|
||||
from flask import current_app
|
||||
|
||||
|
||||
@celery.task(queue="ticket_web")
|
||||
def test_task():
|
||||
current_app.logger.info("test task.............................")
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from flask import Blueprint
|
||||
from flask_restful import Api
|
||||
|
||||
from api.resource import register_resources
|
||||
from .permission import GetResourcesView, HasPermissionView, GetUserInfoView
|
||||
from .account import LoginView, LogoutView
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# account
|
||||
blueprint_account = Blueprint('account_api', __name__, url_prefix='/api')
|
||||
account_rest = Api(blueprint_account)
|
||||
account_rest.add_resource(LoginView, LoginView.url_prefix)
|
||||
account_rest.add_resource(LogoutView, LogoutView.url_prefix)
|
||||
|
||||
# permission
|
||||
blueprint_perm_v01 = Blueprint('permission_api', __name__, url_prefix='/api/v1/perms')
|
||||
perm_rest = Api(blueprint_perm_v01)
|
||||
perm_rest.add_resource(GetResourcesView, GetResourcesView.url_prefix)
|
||||
perm_rest.add_resource(HasPermissionView, HasPermissionView.url_prefix)
|
||||
perm_rest.add_resource(GetUserInfoView, GetUserInfoView.url_prefix)
|
||||
|
||||
|
||||
# cmdb
|
||||
blueprint_cmdb_v01 = Blueprint('cmdb_api_v01', __name__, url_prefix='/api/v0.1')
|
||||
rest = Api(blueprint_cmdb_v01)
|
||||
register_resources(os.path.join(HERE, "cmdb"), rest)
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
import jwt
|
||||
from flask import request
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
from flask_login import login_user, logout_user
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.auth import auth_abandoned
|
||||
from api.models.account import User
|
||||
|
||||
|
||||
class LoginView(APIView):
|
||||
url_prefix = "/login"
|
||||
|
||||
@args_required("username")
|
||||
@args_required("password")
|
||||
@auth_abandoned
|
||||
def post(self):
|
||||
username = request.values.get("username") or request.values.get("email")
|
||||
password = request.values.get("password")
|
||||
user, authenticated = User.query.authenticate(username, password)
|
||||
if not authenticated:
|
||||
return abort(401, "invalid username or password")
|
||||
|
||||
login_user(user)
|
||||
|
||||
token = jwt.encode({
|
||||
'sub': user.email,
|
||||
'iat': datetime.datetime.now(),
|
||||
'exp': datetime.datetime.now() + datetime.timedelta(minutes=24 * 60 * 7)},
|
||||
current_app.config['SECRET_KEY'])
|
||||
|
||||
return self.jsonify(token=token.decode())
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
url_prefix = "/logout"
|
||||
|
||||
@auth_abandoned
|
||||
def post(self):
|
||||
logout_user()
|
||||
self.jsonify(code=200)
|
|
@ -0,0 +1 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.perm.acl import role_required
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.attribute import AttributeManager
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.utils import handle_arg_list
|
||||
|
||||
|
||||
class AttributeSearchView(APIView):
|
||||
url_prefix = ("/attributes/s", "/attributes/search")
|
||||
|
||||
def get(self):
|
||||
q = request.values.get("q")
|
||||
attrs = AttributeManager().get_attributes(name=q)
|
||||
count = len(attrs)
|
||||
return self.jsonify(numfound=count, attributes=attrs)
|
||||
|
||||
|
||||
class AttributeView(APIView):
|
||||
url_prefix = ("/attributes", "/attributes/<string:attr_name>", "/attributes/<int:attr_id>")
|
||||
|
||||
def get(self, attr_name=None, attr_id=None):
|
||||
attr_manager = AttributeManager()
|
||||
attr_dict = None
|
||||
if attr_name is not None:
|
||||
attr_dict = attr_manager.get_attribute_by_name(attr_name)
|
||||
if attr_dict is None:
|
||||
attr_dict = attr_manager.get_attribute_by_alias(attr_name)
|
||||
elif attr_id is not None:
|
||||
attr_dict = attr_manager.get_attribute_by_id(attr_id)
|
||||
if attr_dict is not None:
|
||||
return self.jsonify(attribute=attr_dict)
|
||||
abort(404, "Attribute is not found")
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def post(self):
|
||||
choice_value = handle_arg_list(request.values.get("choice_value"))
|
||||
params = request.values
|
||||
params["choice_value"] = choice_value
|
||||
current_app.logger.debug(params)
|
||||
|
||||
attr_id = AttributeManager.add(**params)
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self, attr_id):
|
||||
choice_value = handle_arg_list(request.values.get("choice_value"))
|
||||
params = request.values
|
||||
params["choice_value"] = choice_value
|
||||
current_app.logger.debug(params)
|
||||
AttributeManager().update(attr_id, **params)
|
||||
return self.jsonify(attr_id=attr_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, attr_id):
|
||||
attr_name = AttributeManager.delete(attr_id)
|
||||
return self. jsonify(message="attribute {0} deleted".format(attr_name))
|
|
@ -0,0 +1,218 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import time
|
||||
|
||||
import six
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
||||
from api.lib.perm.acl import has_perm_from_args
|
||||
from api.lib.cmdb.const import ResourceType, PermEnum
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.const import ExistPolicy
|
||||
from api.lib.cmdb.const import RetKey
|
||||
from api.lib.cmdb.search import Search
|
||||
from api.lib.cmdb.search import SearchError
|
||||
from api.lib.perm.auth import auth_abandoned
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.lib.utils import handle_arg_list
|
||||
from api.models.cmdb import CI
|
||||
from api.resource import APIView
|
||||
|
||||
|
||||
class CIsByTypeView(APIView):
|
||||
url_prefix = "/ci/type/<int:type_id>"
|
||||
|
||||
def get(self, type_id):
|
||||
fields = handle_arg_list(request.values.get("fields", ""))
|
||||
|
||||
ret_key = request.values.get("ret_key", RetKey.NAME)
|
||||
if ret_key not in (RetKey.NAME, RetKey.ALIAS, RetKey.ID):
|
||||
ret_key = RetKey.NAME
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count"))
|
||||
|
||||
manager = CIManager()
|
||||
res = manager.get_cis_by_type(type_id,
|
||||
ret_key=ret_key,
|
||||
fields=fields,
|
||||
page=page,
|
||||
per_page=count)
|
||||
|
||||
return self.jsonify(type_id=type_id,
|
||||
numfound=res[0],
|
||||
total=len(res[2]),
|
||||
page=res[1],
|
||||
cis=res[2])
|
||||
|
||||
|
||||
class CIView(APIView):
|
||||
url_prefix = ("/ci/<int:ci_id>", "/ci")
|
||||
|
||||
def get(self, ci_id):
|
||||
fields = handle_arg_list(request.values.get("fields", ""))
|
||||
|
||||
ret_key = request.values.get("ret_key", RetKey.NAME)
|
||||
if ret_key not in (RetKey.NAME, RetKey.ALIAS, RetKey.ID):
|
||||
ret_key = RetKey.NAME
|
||||
|
||||
manager = CIManager()
|
||||
ci = manager.get_ci_by_id_from_db(ci_id, ret_key=ret_key, fields=fields)
|
||||
return self.jsonify(ci_id=ci_id, ci=ci)
|
||||
|
||||
@staticmethod
|
||||
def _wrap_ci_dict():
|
||||
ci_dict = dict()
|
||||
for k, v in request.values.items():
|
||||
if k != "ci_type" and not k.startswith("_"):
|
||||
ci_dict[k] = v.strip() if isinstance(v, six.string_types) else v
|
||||
return ci_dict
|
||||
|
||||
@has_perm_from_args("ci_type", ResourceType.CI, PermEnum.ADD)
|
||||
def post(self):
|
||||
ci_type = request.values.get("ci_type")
|
||||
_no_attribute_policy = request.values.get("_no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
ci_dict = self._wrap_ci_dict()
|
||||
|
||||
manager = CIManager()
|
||||
current_app.logger.debug(ci_dict)
|
||||
ci_id = manager.add(ci_type,
|
||||
exist_policy=ExistPolicy.REJECT,
|
||||
_no_attribute_policy=_no_attribute_policy, **ci_dict)
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceType.CI, PermEnum.UPDATE, CIManager.get_type_name)
|
||||
def put(self, ci_id=None):
|
||||
args = request.values
|
||||
ci_type = args.get("ci_type")
|
||||
_no_attribute_policy = args.get("_no_attribute_policy", ExistPolicy.IGNORE)
|
||||
|
||||
ci_dict = self._wrap_ci_dict()
|
||||
manager = CIManager()
|
||||
if ci_id is not None:
|
||||
manager.update(ci_id, **ci_dict)
|
||||
else:
|
||||
ci_id = manager.add(ci_type,
|
||||
exist_policy=ExistPolicy.REPLACE,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
**ci_dict)
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceType.CI, PermEnum.DELETE, CIManager.get_type_name)
|
||||
def delete(self, ci_id):
|
||||
manager = CIManager()
|
||||
manager.delete(ci_id)
|
||||
return self.jsonify(message="ok")
|
||||
|
||||
|
||||
class CIDetailView(APIView):
|
||||
url_prefix = "/ci/<int:ci_id>/detail"
|
||||
|
||||
def get(self, ci_id):
|
||||
_ci = CI.get_by_id(ci_id).to_dict()
|
||||
return self.jsonify(**_ci)
|
||||
|
||||
|
||||
class CISearchView(APIView):
|
||||
url_prefix = ("/ci/s", "/ci/search")
|
||||
|
||||
@auth_abandoned
|
||||
def get(self):
|
||||
"""@params: q: query statement
|
||||
fl: filter by column
|
||||
count: the number of ci
|
||||
ret_key: id, name, alias
|
||||
facet: statistic
|
||||
"""
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count"))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
fl = handle_arg_list(request.values.get('fl', ""))
|
||||
ret_key = request.values.get('ret_key', RetKey.NAME)
|
||||
if ret_key not in (RetKey.NAME, RetKey.ALIAS, RetKey.ID):
|
||||
ret_key = RetKey.NAME
|
||||
facet = handle_arg_list(request.values.get("facet", ""))
|
||||
fl = list(filter(lambda x: x != "", fl))
|
||||
facet = list(filter(lambda x: x != "", facet))
|
||||
sort = request.values.get("sort")
|
||||
|
||||
start = time.time()
|
||||
s = Search(query, fl, facet, page, ret_key, count, sort)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError as e:
|
||||
return abort(400, str(e))
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
return abort(500, "search unknown error")
|
||||
current_app.logger.debug("search time is :{0}".format(time.time() - start))
|
||||
return self.jsonify(numfound=numfound,
|
||||
total=total,
|
||||
page=page,
|
||||
facet=facet,
|
||||
counter=counter,
|
||||
result=response)
|
||||
|
||||
|
||||
class CIUnique(APIView):
|
||||
url_prefix = "/ci/<int:ci_id>/unique"
|
||||
|
||||
@has_perm_from_args("ci_id", ResourceType.CI, PermEnum.UPDATE, CIManager.get_type_name)
|
||||
def put(self, ci_id):
|
||||
params = request.values
|
||||
unique_name = params.keys()[0]
|
||||
unique_value = params.values()[0]
|
||||
|
||||
CIManager.update_unique_value(ci_id, unique_name, unique_value)
|
||||
|
||||
return self.jsonify(ci_id=ci_id)
|
||||
|
||||
|
||||
class CIHeartbeatView(APIView):
|
||||
url_prefix = ("/ci/heartbeat", "/ci/heartbeat/<string:ci_type>/<string:unique>")
|
||||
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
ci_type = request.values.get("ci_type", "").strip()
|
||||
try:
|
||||
type_id = CITypeCache.get(ci_type).type_id
|
||||
except AttributeError:
|
||||
return self.jsonify(numfound=0, result=[])
|
||||
agent_status = request.values.get("agent_status")
|
||||
if agent_status:
|
||||
agent_status = int(agent_status)
|
||||
|
||||
numfound, result = CIManager.get_heartbeat(page, type_id, agent_status=agent_status)
|
||||
|
||||
return self.jsonify(numfound=numfound, result=result)
|
||||
|
||||
def post(self, ci_type, unique):
|
||||
if not unique or not ci_type:
|
||||
return self.jsonify(message="error")
|
||||
|
||||
msg, cmd = CIManager().add_heartbeat(ci_type, unique)
|
||||
|
||||
return self.jsonify(message=msg, cmd=cmd)
|
||||
|
||||
|
||||
class CIFlushView(APIView):
|
||||
url_prefix = ("/ci/flush", "/ci/<int:ci_id>/flush")
|
||||
|
||||
@auth_abandoned
|
||||
def get(self, ci_id=None):
|
||||
from api.tasks.cmdb import ci_cache
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
if ci_id is not None:
|
||||
ci_cache.apply_async([ci_id], queue=CMDB_QUEUE)
|
||||
else:
|
||||
cis = CI.get_by(to_dict=False)
|
||||
for ci in cis:
|
||||
ci_cache.apply_async([ci.id], queue=CMDB_QUEUE)
|
||||
return self.jsonify(code=200)
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.ci import CIRelationManager
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
|
||||
class GetSecondCIsView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"
|
||||
|
||||
def get(self, first_ci_id):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count"))
|
||||
relation_type = request.values.get("relation_type", "contain")
|
||||
|
||||
manager = CIRelationManager()
|
||||
numfound, total, second_cis = manager.get_second_cis(
|
||||
first_ci_id, page=page, per_page=count, relation_type=relation_type)
|
||||
|
||||
return self.jsonify(numfound=numfound,
|
||||
total=total,
|
||||
page=page,
|
||||
second_cis=second_cis)
|
||||
|
||||
|
||||
class GetFirstCIsView(APIView):
|
||||
url_prefix = "/ci_relations/<int:second_ci_id>/first_cis"
|
||||
|
||||
def get(self, second_ci_id):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_page_size(request.values.get("count"))
|
||||
|
||||
manager = CIRelationManager()
|
||||
numfound, total, first_cis = manager.get_first_cis(second_ci_id, per_page=count, page=page)
|
||||
|
||||
return self.jsonify(numfound=numfound,
|
||||
total=total,
|
||||
page=page,
|
||||
first_cis=first_cis)
|
||||
|
||||
|
||||
class CIRelationView(APIView):
|
||||
url_prefix = "/ci_relations/<int:first_ci_id>/<int:second_ci_id>"
|
||||
|
||||
def post(self, first_ci_id, second_ci_id):
|
||||
manager = CIRelationManager()
|
||||
res = manager.add(first_ci_id, second_ci_id)
|
||||
return self.jsonify(cr_id=res)
|
||||
|
||||
def delete(self, first_ci_id, second_ci_id):
|
||||
manager = CIRelationManager()
|
||||
manager.delete_2(first_ci_id, second_ci_id)
|
||||
return self.jsonify(message="CIType Relation is deleted")
|
||||
|
||||
|
||||
class DeleteCIRelationView(APIView):
|
||||
url_prefix = "/ci_relations/<int:cr_id>"
|
||||
|
||||
def delete(self, cr_id):
|
||||
manager = CIRelationManager()
|
||||
manager.delete(cr_id)
|
||||
return self.jsonify(message="CIType Relation is deleted")
|
|
@ -0,0 +1,202 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.perm.acl import role_required
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.cache import AttributeCache
|
||||
from api.lib.cmdb.cache import CITypeCache
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.cmdb.ci_type import CITypeGroupManager
|
||||
from api.lib.cmdb.ci_type import CITypeAttributeGroupManager
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.utils import handle_arg_list
|
||||
|
||||
|
||||
class CITypeView(APIView):
|
||||
url_prefix = ("/ci_types", "/ci_types/<int:type_id>", "/ci_types/<string:type_name>")
|
||||
|
||||
def get(self, type_id=None, type_name=None):
|
||||
q = request.args.get("type_name")
|
||||
|
||||
if type_id is not None:
|
||||
ci_types = [CITypeCache.get(type_id).to_dict()]
|
||||
elif type_name is not None:
|
||||
ci_types = [CITypeCache.get(type_name).to_dict()]
|
||||
else:
|
||||
ci_types = CITypeManager().get_ci_types(q)
|
||||
count = len(ci_types)
|
||||
|
||||
return self.jsonify(numfound=count, ci_types=ci_types)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def post(self):
|
||||
params = request.values
|
||||
|
||||
type_name = params.get("name")
|
||||
type_alias = params.get("alias")
|
||||
type_alias = type_name if not type_alias else type_alias
|
||||
params['alias'] = type_alias
|
||||
|
||||
manager = CITypeManager()
|
||||
type_id = manager.add(**params)
|
||||
|
||||
return self.jsonify(type_id=type_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self, type_id):
|
||||
params = request.values
|
||||
|
||||
manager = CITypeManager()
|
||||
manager.update(type_id, **params)
|
||||
return self.jsonify(type_id=type_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, type_id):
|
||||
CITypeManager.delete(type_id)
|
||||
return self.jsonify(type_id=type_id)
|
||||
|
||||
|
||||
class CITypeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/groups", "/ci_types/groups/<int:gid>")
|
||||
|
||||
def get(self):
|
||||
need_other = request.values.get("need_other")
|
||||
return self.jsonify(CITypeGroupManager.get(need_other))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def post(self):
|
||||
name = request.values.get("name")
|
||||
group = CITypeGroupManager.add(name)
|
||||
return self.jsonify(group.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self, gid):
|
||||
name = request.values.get('name')
|
||||
type_ids = request.values.get('type_ids')
|
||||
CITypeGroupManager.update(gid, name, type_ids)
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, gid):
|
||||
CITypeGroupManager.delete(gid)
|
||||
return self.jsonify(gid=gid)
|
||||
|
||||
|
||||
class CITypeQueryView(APIView):
|
||||
url_prefix = "/ci_types/query"
|
||||
|
||||
@args_required("q")
|
||||
def get(self):
|
||||
q = request.args.get("q")
|
||||
res = CITypeManager.query(q)
|
||||
return self.jsonify(ci_type=res)
|
||||
|
||||
|
||||
class EnableCITypeView(APIView):
|
||||
url_prefix = "/ci_types/<int:type_id>/enable"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def post(self, type_id):
|
||||
enable = request.values.get("enable", True)
|
||||
CITypeManager.set_enabled(type_id, enabled=enable)
|
||||
return self.jsonify(type_id=type_id, enable=enable)
|
||||
|
||||
|
||||
class CITypeAttributeView(APIView):
|
||||
url_prefix = ("/ci_types/<int:type_id>/attributes", "/ci_types/<string:type_name>/attributes")
|
||||
|
||||
def get(self, type_id=None, type_name=None):
|
||||
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, "CIType does not exist")
|
||||
type_id = t.id
|
||||
unique_id = t.unique_id
|
||||
unique = AttributeCache.get(unique_id).name
|
||||
return self.jsonify(attributes=CITypeAttributeManager.get_attributes_by_type_id(type_id),
|
||||
type_id=type_id,
|
||||
unique_id=unique_id,
|
||||
unique=unique)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("attr_id")
|
||||
def post(self, type_id=None):
|
||||
attr_id_list = handle_arg_list(request.values.get("attr_id"))
|
||||
params = request.values
|
||||
params.pop("attr_id", "")
|
||||
|
||||
CITypeAttributeManager.add(type_id, attr_id_list, **params)
|
||||
return self.jsonify(attributes=attr_id_list)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("attributes")
|
||||
def put(self, type_id=None):
|
||||
"""
|
||||
attributes is list, only support raw data request
|
||||
:param type_id:
|
||||
:return:
|
||||
"""
|
||||
attributes = request.values.get("attributes")
|
||||
current_app.logger.debug(attributes)
|
||||
if not isinstance(attributes, list):
|
||||
return abort(400, "attributes must be list")
|
||||
CITypeAttributeManager.update(type_id, attributes)
|
||||
return self.jsonify(attributes=attributes)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("attr_id")
|
||||
def delete(self, type_id=None):
|
||||
"""
|
||||
Form request: attr_id is a string, separated by commas
|
||||
Raw data request: attr_id is a list
|
||||
:param type_id:
|
||||
:return:
|
||||
"""
|
||||
attr_id_list = handle_arg_list(request.values.get("attr_id", ""))
|
||||
|
||||
CITypeAttributeManager.delete(type_id, attr_id_list)
|
||||
|
||||
return self.jsonify(attributes=attr_id_list)
|
||||
|
||||
|
||||
class CITypeAttributeGroupView(APIView):
|
||||
url_prefix = ("/ci_types/<int:type_id>/attribute_groups",
|
||||
"/ci_types/attribute_groups/<int:group_id>")
|
||||
|
||||
def get(self, type_id):
|
||||
need_other = request.values.get("need_other")
|
||||
return self.jsonify(CITypeAttributeGroupManager.get_by_type_id(type_id, need_other))
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def post(self, type_id):
|
||||
name = request.values.get("name").strip()
|
||||
order = request.values.get("order") or 0
|
||||
attrs = handle_arg_list(request.values.get("attributes", ""))
|
||||
orders = list(range(len(attrs)))
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
group = CITypeAttributeGroupManager.create_or_update(type_id, name, attr_order, order)
|
||||
current_app.logger.warning(group.id)
|
||||
return self.jsonify(group_id=group.id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def put(self, group_id):
|
||||
name = request.values.get("name")
|
||||
order = request.values.get("order") or 0
|
||||
attrs = handle_arg_list(request.values.get("attributes", ""))
|
||||
orders = list(range(len(attrs)))
|
||||
|
||||
attr_order = list(zip(attrs, orders))
|
||||
CITypeAttributeGroupManager.update(group_id, name, attr_order, order)
|
||||
return self.jsonify(group_id=group_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, group_id):
|
||||
CITypeAttributeGroupManager.delete(group_id)
|
||||
return self.jsonify(group_id=group_id)
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.lib.perm.acl import role_required
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.cmdb.ci_type import CITypeRelationManager
|
||||
from api.lib.decorator import args_required
|
||||
from api.resource import APIView
|
||||
|
||||
|
||||
class GetChildrenView(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:parent_id>/children"
|
||||
|
||||
def get(self, parent_id):
|
||||
return self.jsonify(children=CITypeRelationManager.get_children(parent_id))
|
||||
|
||||
|
||||
class GetParentsView(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:child_id>/parents"
|
||||
|
||||
def get(self, child_id):
|
||||
return self.jsonify(parents=CITypeRelationManager.get_parents(child_id))
|
||||
|
||||
|
||||
class CITypeRelationView(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:parent_id>/<int:child_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("relation_type_id")
|
||||
def post(self, parent_id, child_id):
|
||||
relation_type_id = request.values.get("relation_type_id")
|
||||
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id)
|
||||
return self.jsonify(ctr_id=ctr_id)
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, parent_id, child_id):
|
||||
CITypeRelationManager.delete_2(parent_id, child_id)
|
||||
return self.jsonify(code=200, parent_id=parent_id, child_id=child_id)
|
||||
|
||||
|
||||
class CITypeRelationDelete2View(APIView):
|
||||
url_prefix = "/ci_type_relations/<int:ctr_id>"
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, ctr_id):
|
||||
CITypeRelationManager.delete(ctr_id)
|
||||
return self.jsonify(code=200, ctr_id=ctr_id)
|
|
@ -0,0 +1,63 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from flask import abort
|
||||
from flask import request
|
||||
|
||||
from api.lib.cmdb.history import AttributeHistoryManger
|
||||
from api.lib.utils import get_page
|
||||
from api.lib.utils import get_page_size
|
||||
from api.resource import APIView
|
||||
|
||||
|
||||
class RecordView(APIView):
|
||||
url_prefix = "/history/records"
|
||||
|
||||
def get(self):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
page_size = get_page_size(request.values.get("page_size"))
|
||||
_start = request.values.get("start")
|
||||
_end = request.values.get("end")
|
||||
username = request.values.get("username", "")
|
||||
start, end = None, None
|
||||
if _start:
|
||||
try:
|
||||
start = datetime.datetime.strptime(_start, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
abort(400, 'incorrect start date time')
|
||||
if _end:
|
||||
try:
|
||||
end = datetime.datetime.strptime(_end, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
abort(400, 'incorrect end date time')
|
||||
|
||||
numfound, total, res = AttributeHistoryManger.get_records(start, end, username, page, page_size)
|
||||
|
||||
return self.jsonify(numfound=numfound,
|
||||
records=res,
|
||||
page=page,
|
||||
total=total,
|
||||
start=_start,
|
||||
end=_end,
|
||||
username=username)
|
||||
|
||||
|
||||
class CIHistoryView(APIView):
|
||||
url_prefix = "/history/ci/<int:ci_id>"
|
||||
|
||||
def get(self, ci_id):
|
||||
result = AttributeHistoryManger.get_by_ci_id(ci_id)
|
||||
return self.jsonify(result)
|
||||
|
||||
|
||||
class RecordDetailView(APIView):
|
||||
url_prefix = "/history/records/<int:record_id>"
|
||||
|
||||
def get(self, record_id):
|
||||
username, timestamp, attr_dict, rel_dict = AttributeHistoryManger.get_record_detail(record_id)
|
||||
return self.jsonify(username=username,
|
||||
timestamp=timestamp,
|
||||
attr_history=attr_dict,
|
||||
rel_history=rel_dict)
|
|
@ -0,0 +1,89 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.perm.acl import has_perm_from_args
|
||||
from api.lib.cmdb.const import ResourceType, PermEnum
|
||||
from api.lib.cmdb.preference import PreferenceManager
|
||||
from api.lib.cmdb.ci import CIManager
|
||||
from api.lib.cmdb.ci_type import CITypeManager
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.utils import handle_arg_list
|
||||
|
||||
|
||||
class PreferenceShowCITypesView(APIView):
|
||||
url_prefix = "/preference/ci_types"
|
||||
|
||||
def get(self):
|
||||
instance = request.values.get("instance")
|
||||
tree = request.values.get("tree")
|
||||
return self.jsonify(PreferenceManager.get_types(instance, tree))
|
||||
|
||||
|
||||
class PreferenceShowAttributesView(APIView):
|
||||
url_prefix = "/preference/ci_types/<id_or_name>/attributes"
|
||||
|
||||
def get(self, id_or_name):
|
||||
is_subscribed, attributes = PreferenceManager.get_show_attributes(id_or_name)
|
||||
return self.jsonify(attributes=attributes, is_subscribed=is_subscribed)
|
||||
|
||||
@has_perm_from_args("id_or_name", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
|
||||
@args_required("attr")
|
||||
def post(self, id_or_name):
|
||||
id_or_name = int(id_or_name)
|
||||
attr_list = handle_arg_list(request.values.get("attr", ""))
|
||||
orders = list(range(len(attr_list)))
|
||||
PreferenceManager.create_or_update_show_attributes(id_or_name, list(zip(attr_list, orders)))
|
||||
return self.jsonify(type_id=id_or_name,
|
||||
attr_order=list(zip(attr_list, orders)))
|
||||
|
||||
@has_perm_from_args("id_or_name", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
|
||||
def put(self, id_or_name):
|
||||
self.post(id_or_name)
|
||||
|
||||
|
||||
class PreferenceTreeApiView(APIView):
|
||||
url_prefix = "/preference/tree/view"
|
||||
|
||||
def get(self):
|
||||
return self.jsonify(PreferenceManager.get_tree_view())
|
||||
|
||||
@has_perm_from_args("type_id", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
|
||||
@args_required("type_id")
|
||||
@args_required("levels")
|
||||
def post(self):
|
||||
type_id = request.values.get("type_id")
|
||||
levels = handle_arg_list(request.values.get("levels"))
|
||||
res = PreferenceManager.create_or_update_tree_view(type_id, levels)
|
||||
return self.jsonify(res and res.to_dict() or {})
|
||||
|
||||
def put(self):
|
||||
self.post()
|
||||
|
||||
|
||||
class PreferenceRelationApiView(APIView):
|
||||
url_prefix = "/preference/relation/view"
|
||||
|
||||
def get(self):
|
||||
return self.jsonify(PreferenceManager.get_relation_view())
|
||||
|
||||
@has_perm_from_args("parent_id", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
|
||||
@has_perm_from_args("child_id", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
|
||||
@args_required("name")
|
||||
def post(self):
|
||||
name = request.values.get("name")
|
||||
parent_id = request.values.get("parent_id")
|
||||
child_id = request.values.get("child_id")
|
||||
res = PreferenceManager.create_or_update_relation_view(name, parent_id, child_id)
|
||||
return self.jsonify(res.to_dict())
|
||||
|
||||
def put(self):
|
||||
self.post()
|
||||
|
||||
@args_required("name")
|
||||
def delete(self):
|
||||
name = request.values.get("name")
|
||||
PreferenceManager.delete_relation_view(name)
|
||||
return self.jsonify(name=name)
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import request
|
||||
from flask import abort
|
||||
|
||||
from api.resource import APIView
|
||||
from api.lib.perm.acl import role_required
|
||||
from api.lib.cmdb.const import RoleEnum
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.cmdb.relation_type import RelationTypeManager
|
||||
|
||||
|
||||
class RelationTypeView(APIView):
|
||||
url_prefix = ("/relation_types", "/relation_types/<int:rel_id>")
|
||||
|
||||
def get(self):
|
||||
return self.jsonify([i.to_dict() for i in RelationTypeManager.get_all()])
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def post(self):
|
||||
name = request.values.get("name") or abort(400, "Name cannot be empty")
|
||||
rel = RelationTypeManager.add(name)
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
@args_required("name")
|
||||
def put(self, rel_id):
|
||||
name = request.values.get("name") or abort(400, "Name cannot be empty")
|
||||
rel = RelationTypeManager.update(rel_id, name)
|
||||
return self.jsonify(rel.to_dict())
|
||||
|
||||
@role_required(RoleEnum.CONFIG)
|
||||
def delete(self, rel_id):
|
||||
RelationTypeManager.delete(rel_id)
|
||||
return self.jsonify(rel_id=rel_id)
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
|
||||
from api.lib.decorator import args_required
|
||||
from api.lib.perm.acl import ACLManager
|
||||
from api.lib.perm.acl import validate_permission
|
||||
from api.resource import APIView
|
||||
|
||||
|
||||
class HasPermissionView(APIView):
|
||||
url_prefix = "/validate"
|
||||
|
||||
@args_required("resource")
|
||||
@args_required("resource_type")
|
||||
@args_required("perm")
|
||||
def get(self):
|
||||
resource = request.values.get("resource")
|
||||
resource_type = request.values.get("resource_type")
|
||||
perm = request.values.get("perm")
|
||||
validate_permission(resource, resource_type, perm)
|
||||
return self.jsonify(is_valid=True)
|
||||
|
||||
def post(self):
|
||||
self.get()
|
||||
|
||||
|
||||
class GetResourcesView(APIView):
|
||||
url_prefix = "/resources"
|
||||
|
||||
@args_required("resource_type")
|
||||
def get(self):
|
||||
resource_type = request.values.get("resource_type")
|
||||
res = ACLManager().get_resources(resource_type)
|
||||
return self.jsonify(res)
|
||||
|
||||
|
||||
class GetUserInfoView(APIView):
|
||||
url_prefix = "/user/info"
|
||||
|
||||
def get(self):
|
||||
name = session.get("acl", {}).get("nickName") or session.get("CAS_USERNAME") or current_user.nickname
|
||||
role = dict(permissions=session.get("acl", {}).get("parentRoles", []) or ["admin"])
|
||||
avatar = session.get("acl", {}).get("avatar") or current_user.avatar
|
||||
return self.jsonify(result=dict(name=name,
|
||||
role=role,
|
||||
avatar=avatar))
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Create an application instance."""
|
||||
from flask import g
|
||||
from flask_login import current_user
|
||||
|
||||
from api.app import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.user = current_user
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.app import create_app
|
||||
from api.extensions import celery
|
||||
|
||||
# celery worker -A celery_worker.celery -l DEBUG -E -Q xxxx
|
||||
|
||||
app = create_app()
|
||||
app.app_context().push()
|
11
cmdb_api.md
11
cmdb_api.md
|
@ -1,13 +1,12 @@
|
|||
# CMDB API文档
|
||||
|
||||
## 状态返回码的定义
|
||||
* 200: 成功
|
||||
* 200:成功
|
||||
* 400:失败
|
||||
* 401:未授权
|
||||
* 404:url not found
|
||||
* 408:超时
|
||||
* 410:资源删除
|
||||
* 500: 服务器错误
|
||||
* 401:未认证
|
||||
* 403:no permission
|
||||
* 404:not found
|
||||
* 500:服务器未知错误
|
||||
|
||||
|
||||
## 用户接口
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,61 +0,0 @@
|
|||
# coding: utf-8
|
||||
# common
|
||||
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'dsfdjsf@3213!@JKJWL'
|
||||
HOST = 'http://127.0.0.1:5000'
|
||||
|
||||
# # database
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://mysqluser:password@127.0.0.1:3306/cmdb?charset=utf8'
|
||||
|
||||
SQLALCHEMY_ECHO = False
|
||||
SQLALCHEMY_POOL_SIZE = 10
|
||||
SQLALCHEMY_POOL_RECYCLE = 300
|
||||
|
||||
# # cache
|
||||
CACHE_TYPE = "redis"
|
||||
CACHE_REDIS_HOST = "127.0.0.1"
|
||||
CACHE_REDIS_PORT = 6379
|
||||
CACHE_KEY_PREFIX = "CMDB-API"
|
||||
CACHE_DEFAULT_TIMEOUT = 3000
|
||||
|
||||
|
||||
# # CI cache
|
||||
REDIS_DB = 0
|
||||
REDIS_MAX_CONN = 30
|
||||
|
||||
|
||||
# # queue
|
||||
CELERY_RESULT_BACKEND = "redis://127.0.0.1//"
|
||||
BROKER_URL = 'redis://127.0.0.1//'
|
||||
BROKER_VHOST = '/'
|
||||
|
||||
|
||||
# # i18n
|
||||
ACCEPT_LANGUAGES = ['en', 'zh']
|
||||
BABEL_DEFAULT_LOCALE = 'zh'
|
||||
BABEL_DEFAULT_TIMEZONE = 'Asia/Shanghai'
|
||||
|
||||
# # log
|
||||
LOG_PATH = './logs/app.log'
|
||||
|
||||
LOG_LEVEL = 'DEBUG'
|
||||
ADMINS = ('@')
|
||||
|
||||
# # mail
|
||||
MAIL_SERVER = ''
|
||||
MAIL_PORT = 25
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USE_SSL = False
|
||||
MAIL_DEBUG = True
|
||||
MAIL_USERNAME = ''
|
||||
MAIL_PASSWORD = ''
|
||||
DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
|
||||
# # pagination
|
||||
PER_PAGE_COUNT_RANGE = (10, 25, 50, 100)
|
||||
DEFAULT_PAGE_COUNT = 25
|
||||
|
||||
|
||||
WHITE_LIST = ["127.0.0.1"]
|
|
@ -1,11 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from attribute import attribute
|
||||
from ci_type import citype
|
||||
from ci_type_relation import cityperelation
|
||||
from ci_relation import cirelation
|
||||
from ci import ci
|
||||
from history import history
|
||||
from account import account
|
||||
from special import special
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
|
||||
from flask import g
|
||||
from flask import abort
|
||||
from flask import jsonify
|
||||
|
||||
from models import row2dict
|
||||
from lib.account import AccountManager
|
||||
from lib.auth import auth_with_key
|
||||
|
||||
|
||||
account = Blueprint('account', __name__)
|
||||
|
||||
|
||||
@account.route("/<int:uid>", methods=["GET"])
|
||||
@auth_with_key
|
||||
def get_user(uid=None):
|
||||
manager = AccountManager()
|
||||
user = manager.get_user_by_uid(uid)
|
||||
if user:
|
||||
return jsonify(rolenames=user.rolenames, user=row2dict(user))
|
||||
else:
|
||||
return jsonify(user=None)
|
||||
|
||||
|
||||
@account.route("", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_user():
|
||||
manager = AccountManager()
|
||||
params = {}
|
||||
for k, v in request.values.iteritems():
|
||||
params[k] = v
|
||||
user = manager.create_user(**params)
|
||||
return jsonify(user=row2dict(user))
|
||||
|
||||
|
||||
@account.route("/<int:uid>", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_user(uid=None):
|
||||
manager = AccountManager()
|
||||
params = {}
|
||||
for k, v in request.values.iteritems():
|
||||
params[k] = v
|
||||
ret, res = manager.update_user(uid, **params)
|
||||
if not ret:
|
||||
abort(res[0], res[1])
|
||||
return jsonify(user=row2dict(res), rolenames=res.rolenames)
|
||||
|
||||
|
||||
@account.route("/<int:uid>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_user(uid=None):
|
||||
manager = AccountManager()
|
||||
ret, res = manager.delete_user(uid)
|
||||
if not ret:
|
||||
abort(res[0], res[1])
|
||||
return jsonify(uid=uid)
|
||||
|
||||
|
||||
@account.route("/validate", methods=["POST"])
|
||||
@auth_with_key
|
||||
def validate():
|
||||
username = request.values.get("username")
|
||||
password = request.values.get("password")
|
||||
manager = AccountManager()
|
||||
user, authenticated = manager.validate(username, password)
|
||||
if user and not authenticated:
|
||||
return jsonify(code=401, user=row2dict(user), rolenames=user.rolenames)
|
||||
elif not user:
|
||||
return jsonify(code=404, message="user is not existed")
|
||||
return jsonify(code=200, user=row2dict(user), rolenames=user.rolenames)
|
||||
|
||||
|
||||
@account.route("/key", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_key():
|
||||
manager = AccountManager()
|
||||
ret, res = manager.reset_key(g.user.uid)
|
||||
if not ret:
|
||||
abort(res[0], res[1])
|
||||
return jsonify(user=row2dict(res), rolenames=res.rolenames)
|
||||
|
||||
|
||||
@account.route("/password", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_password():
|
||||
manager = AccountManager()
|
||||
old = request.values.get("password")
|
||||
new = request.values.get("new_password")
|
||||
confirm = request.values.get("confirm")
|
||||
ret, res = manager.update_password(g.user.uid, old, new, confirm)
|
||||
if not ret:
|
||||
abort(res[0], res[1])
|
||||
return jsonify(user=row2dict(res), rolenames=res.rolenames)
|
|
@ -1,152 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import jsonify
|
||||
from flask import request
|
||||
from flask import Blueprint
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
|
||||
from lib.attribute import AttributeManager
|
||||
from lib.ci_type import CITypeAttributeManager
|
||||
from lib.decorator import argument_required
|
||||
from lib.exception import InvalidUsageError
|
||||
from lib.auth import auth_with_key
|
||||
|
||||
attribute = Blueprint("attribute", __name__)
|
||||
|
||||
|
||||
@attribute.route("", methods=["GET"])
|
||||
def get_attributes():
|
||||
q = request.values.get("q")
|
||||
attrs = AttributeManager().get_attributes(name=q)
|
||||
count = len(attrs)
|
||||
return jsonify(numfound=count, attributes=attrs)
|
||||
|
||||
|
||||
@attribute.route("/<string:attr_name>", methods=["GET"])
|
||||
@attribute.route("/<int:attr_id>", methods=["GET"])
|
||||
def get_attribute(attr_name=None, attr_id=None):
|
||||
attr_manager = AttributeManager()
|
||||
attr_dict = None
|
||||
if attr_name is not None:
|
||||
attr_dict = attr_manager.get_attribute_by_name(attr_name)
|
||||
if attr_dict is None:
|
||||
attr_dict = attr_manager.get_attribute_by_alias(attr_name)
|
||||
elif attr_id is not None:
|
||||
attr_dict = attr_manager.get_attribute_by_id(attr_id)
|
||||
if attr_dict is not None:
|
||||
return jsonify(attribute=attr_dict)
|
||||
abort(404, "attribute not found")
|
||||
|
||||
|
||||
@attribute.route("", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_attribute():
|
||||
with argument_required("attr_name"):
|
||||
attr_name = request.values.get("attr_name")
|
||||
current_app.logger.info(attr_name)
|
||||
attr_alias = request.values.get("attr_alias", attr_name)
|
||||
choice_value = request.values.get("choice_value")
|
||||
is_multivalue = request.values.get("is_multivalue", False)
|
||||
is_uniq = request.values.get("is_uniq", False)
|
||||
is_index = request.values.get("is_index", False)
|
||||
value_type = request.values.get("value_type", "text")
|
||||
try:
|
||||
is_multivalue = int(is_multivalue)
|
||||
is_uniq = int(is_uniq)
|
||||
is_index = int(is_index)
|
||||
except ValueError:
|
||||
raise InvalidUsageError("argument format is error")
|
||||
attr_manager = AttributeManager()
|
||||
kwargs = {"choice_value": choice_value, "is_multivalue": is_multivalue,
|
||||
"is_uniq": is_uniq, "value_type": value_type,
|
||||
"is_index": is_index}
|
||||
ret, res = attr_manager.add(attr_name, attr_alias, **kwargs)
|
||||
if not ret:
|
||||
return abort(500, res)
|
||||
return jsonify(attr_id=res)
|
||||
|
||||
|
||||
@attribute.route("/<int:attr_id>", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_attribute(attr_id=None):
|
||||
with argument_required("attr_name"):
|
||||
attr_name = request.values.get("attr_name")
|
||||
attr_alias = request.values.get("attr_alias", attr_name)
|
||||
choice_value = request.values.get("choice_value")
|
||||
is_multivalue = request.values.get("is_multivalue", False)
|
||||
is_uniq = request.values.get("is_uniq", False)
|
||||
value_type = request.values.get("value_type", "text")
|
||||
try:
|
||||
is_multivalue = int(is_multivalue)
|
||||
is_uniq = int(is_uniq)
|
||||
except ValueError:
|
||||
raise InvalidUsageError("argument format is error")
|
||||
attr_manager = AttributeManager()
|
||||
kwargs = {"choice_value": choice_value, "is_multivalue": is_multivalue,
|
||||
"is_uniq": is_uniq, "value_type": value_type}
|
||||
ret, res = attr_manager.update(attr_id, attr_name,
|
||||
attr_alias, **kwargs)
|
||||
if not ret:
|
||||
return abort(500, res)
|
||||
return jsonify(attr_id=res)
|
||||
|
||||
|
||||
@attribute.route("/<int:attr_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_attribute(attr_id=None):
|
||||
attr_manager = AttributeManager()
|
||||
res = attr_manager.delete(attr_id)
|
||||
return jsonify(message="attribute {0} deleted".format(res))
|
||||
|
||||
|
||||
@attribute.route("/citype/<int:type_id>", methods=["GET"])
|
||||
@attribute.route("/citype/<string:type_name>", methods=["GET"])
|
||||
def get_attributes_by_type(type_id=None, type_name=None):
|
||||
manager = CITypeAttributeManager()
|
||||
from models.attribute import CIAttributeCache
|
||||
from models.ci_type import CITypeCache
|
||||
from models.ci_type import CITypeAttributeCache
|
||||
|
||||
t = CITypeCache.get(type_id)
|
||||
if not t:
|
||||
t = CITypeCache.get(type_name)
|
||||
if not t:
|
||||
return abort(400, "CIType {0} is not existed".format(type_id))
|
||||
type_id = t.type_id
|
||||
uniq_id = t.uniq_id
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
unique = CIAttributeCache.get(uniq_id).attr_name
|
||||
return jsonify(attributes=manager.get_attributes_by_type_id(type_id),
|
||||
type_id=type_id, uniq_id=uniq_id, unique=unique)
|
||||
|
||||
|
||||
@attribute.route("/citype/<int:type_id>", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_attributes_to_citype(type_id=None):
|
||||
with argument_required("attr_id"):
|
||||
attr_ids = request.values.get("attr_id", "")
|
||||
is_required = request.values.get("is_required", False)
|
||||
attr_id_list = attr_ids.strip().split(",")
|
||||
if "" in attr_id_list:
|
||||
attr_id_list.remove("")
|
||||
attr_id_list = map(int, attr_id_list)
|
||||
try:
|
||||
is_required = int(is_required)
|
||||
except ValueError:
|
||||
abort(500, "argument format is error")
|
||||
manager = CITypeAttributeManager()
|
||||
manager.add(type_id, attr_id_list, is_required=is_required)
|
||||
return jsonify(attributes=attr_id_list)
|
||||
|
||||
|
||||
@attribute.route("/citype/<int:type_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_attribute_in_type(type_id=None):
|
||||
with argument_required("attr_id"):
|
||||
attr_ids = request.values.get("attr_id", "")
|
||||
attr_id_list = attr_ids.strip().split(",")
|
||||
manager = CITypeAttributeManager()
|
||||
manager.delete(type_id, attr_id_list)
|
||||
return jsonify(attributes=attr_id_list)
|
216
core/ci.py
216
core/ci.py
|
@ -1,216 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import sys
|
||||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
import time
|
||||
import urllib
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
from flask import jsonify
|
||||
from flask import current_app
|
||||
from flask import make_response
|
||||
from flask import render_template
|
||||
from flask import abort
|
||||
|
||||
from lib.auth import auth_with_key
|
||||
from lib.ci import CIManager
|
||||
from lib.ci import HostNumStatis
|
||||
from lib.search import Search
|
||||
from lib.search import SearchError
|
||||
from lib.utils import get_page
|
||||
from lib.utils import get_per_page
|
||||
from models.ci_type import CITypeCache
|
||||
|
||||
ci = Blueprint("ci", __name__)
|
||||
|
||||
|
||||
@ci.route("/type/<int:type_id>", methods=["GET"])
|
||||
def get_cis_by_type(type_id=None):
|
||||
fields = request.args.get("fields", "").strip().split(",")
|
||||
fields = filter(lambda x: x != "", fields)
|
||||
|
||||
ret_key = request.args.get("ret_key", "name")
|
||||
if ret_key not in ('name', 'alias', 'id'):
|
||||
ret_key = 'name'
|
||||
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_per_page(request.values.get("count"))
|
||||
manager = CIManager()
|
||||
res = manager.get_cis_by_type(type_id, ret_key=ret_key,
|
||||
fields=fields, page=page, per_page=count)
|
||||
return jsonify(type_id=type_id, numfound=res[0],
|
||||
total=len(res[2]), page=res[1], cis=res[2])
|
||||
|
||||
|
||||
@ci.route("/<int:ci_id>", methods=['GET'])
|
||||
def get_ci(ci_id=None):
|
||||
fields = request.args.get("fields", "").strip().split(",")
|
||||
fields = filter(lambda x: x != "", fields)
|
||||
|
||||
ret_key = request.args.get("ret_key", "name")
|
||||
if ret_key not in ('name', 'alias', 'id'):
|
||||
ret_key = 'name'
|
||||
|
||||
manager = CIManager()
|
||||
ci = manager.get_ci_by_id(ci_id, ret_key=ret_key, fields=fields)
|
||||
return jsonify(ci_id=ci_id, ci=ci)
|
||||
|
||||
|
||||
@ci.route("/s", methods=["GET"])
|
||||
@ci.route("/search", methods=["GET"])
|
||||
def search():
|
||||
"""@params: q: query statement
|
||||
fl: filter by column
|
||||
count: the number of ci
|
||||
ret_key: id, name, alias
|
||||
facet: statistic
|
||||
wt: result format
|
||||
"""
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_per_page(request.values.get("count"))
|
||||
|
||||
query = request.values.get('q', "")
|
||||
fl = request.values.get('fl', "").split(",")
|
||||
ret_key = request.values.get('ret_key', "name")
|
||||
if ret_key not in ('name', 'alias', 'id'):
|
||||
ret_key = 'name'
|
||||
facet = request.values.get("facet", "").split(",")
|
||||
wt = request.values.get('wt', 'json')
|
||||
fl = filter(lambda x: x != "", fl)
|
||||
facet = filter(lambda x: x != "", facet)
|
||||
sort = request.values.get("sort")
|
||||
|
||||
start = time.time()
|
||||
s = Search(query, fl, facet, page, ret_key, count, sort)
|
||||
try:
|
||||
response, counter, total, page, numfound, facet = s.search()
|
||||
except SearchError, e:
|
||||
return abort(400, str(e))
|
||||
except Exception, e:
|
||||
current_app.logger.error(str(e))
|
||||
return abort(500, "search unknown error")
|
||||
|
||||
if wt == 'xml':
|
||||
res = make_response(
|
||||
render_template("search.xml",
|
||||
counter=counter,
|
||||
total=total,
|
||||
result=response,
|
||||
page=page,
|
||||
numfound=numfound,
|
||||
facet=facet))
|
||||
res.headers['Content-type'] = 'text/xml'
|
||||
return res
|
||||
current_app.logger.debug("search time is :{0}".format(
|
||||
time.time() - start))
|
||||
return jsonify(numfound=numfound,
|
||||
total=total,
|
||||
page=page,
|
||||
facet=facet,
|
||||
counter=counter,
|
||||
result=response)
|
||||
|
||||
|
||||
@ci.route("", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_ci():
|
||||
ci_type = request.values.get("ci_type")
|
||||
_no_attribute_policy = request.values.get("_no_attribute_policy", "ignore")
|
||||
|
||||
ci_dict = dict()
|
||||
for k, v in request.values.iteritems():
|
||||
if k != "ci_type" and not k.startswith("_"):
|
||||
ci_dict[k] = v.strip()
|
||||
|
||||
manager = CIManager()
|
||||
current_app.logger.debug(ci_dict)
|
||||
ci_id = manager.add(ci_type, exist_policy="reject",
|
||||
_no_attribute_policy=_no_attribute_policy, **ci_dict)
|
||||
return jsonify(ci_id=ci_id)
|
||||
|
||||
|
||||
@ci.route("", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_ci():
|
||||
if request.data:
|
||||
args = dict()
|
||||
_args = request.data.split("&")
|
||||
for arg in _args:
|
||||
if arg:
|
||||
args[arg.split("=")[0]] = \
|
||||
urllib.unquote(urllib.unquote(arg.split("=")[1]))
|
||||
else:
|
||||
args = request.values
|
||||
|
||||
ci_type = args.get("ci_type")
|
||||
_no_attribute_policy = args.get("_no_attribute_policy", "ignore")
|
||||
ci_dict = dict()
|
||||
for k, v in args.items():
|
||||
if k != "ci_type" and not k.startswith("_"):
|
||||
ci_dict[k] = v.strip()
|
||||
|
||||
manager = CIManager()
|
||||
ci_id = manager.add(ci_type, exist_policy="replace",
|
||||
_no_attribute_policy=_no_attribute_policy, **ci_dict)
|
||||
return jsonify(ci_id=ci_id)
|
||||
|
||||
|
||||
@ci.route("/<int:ci_id>/unique", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_ci_unique(ci_id):
|
||||
m = CIManager()
|
||||
m.update_unique_value(ci_id, request.values)
|
||||
return jsonify(ci_id=ci_id)
|
||||
|
||||
|
||||
@ci.route("/<int:ci_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_ci(ci_id=None):
|
||||
manager = CIManager()
|
||||
manager.delete(ci_id)
|
||||
return jsonify(message="ok")
|
||||
|
||||
|
||||
@ci.route("/heartbeat/<string:ci_type>/<string:unique>", methods=["POST"])
|
||||
def add_heartbeat(ci_type, unique):
|
||||
if not unique or not ci_type:
|
||||
return jsonify(message="error")
|
||||
# return jsonify(message="ok")
|
||||
return jsonify(message=CIManager().add_heartbeat(ci_type, unique))
|
||||
|
||||
|
||||
@ci.route("/heartbeat", methods=["GET"])
|
||||
def get_heartbeat():
|
||||
page = get_page(request.values.get("page", 1))
|
||||
ci_type = request.values.get("ci_type", "").strip()
|
||||
try:
|
||||
ci_type = CITypeCache.get(ci_type).type_id
|
||||
except:
|
||||
return jsonify(numfound=0, result=[])
|
||||
agent_status = request.values.get("agent_status", None)
|
||||
if agent_status:
|
||||
agent_status = int(agent_status)
|
||||
numfound, result = CIManager().get_heartbeat(page,
|
||||
ci_type,
|
||||
agent_status=agent_status)
|
||||
return jsonify(numfound=numfound, result=result)
|
||||
|
||||
|
||||
######################### just for frontend ###################################
|
||||
|
||||
@ci.route("/hosts/nums", methods=["GET"])
|
||||
def get_hosts_nums():
|
||||
ci_type = request.args.get("ci_type", "").strip()
|
||||
ci_ids = request.args.get("ci_ids", "").strip()
|
||||
ci_id_list = ci_ids.split(",")
|
||||
ci_id_list = map(str, filter(lambda x: x != "", ci_id_list))
|
||||
res = {}
|
||||
if ci_type == "bu":
|
||||
res = HostNumStatis().get_hosts_by_bu(ci_id_list)
|
||||
elif ci_type == "product":
|
||||
res = HostNumStatis().get_hosts_by_product(ci_id_list)
|
||||
elif ci_type == "project":
|
||||
res = HostNumStatis().get_hosts_by_project(ci_id_list)
|
||||
return jsonify(hosts=res)
|
|
@ -1,70 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import jsonify
|
||||
from flask import request
|
||||
|
||||
from lib.ci import CIRelationManager
|
||||
from lib.utils import get_page
|
||||
from lib.utils import get_per_page
|
||||
from lib.auth import auth_with_key
|
||||
|
||||
|
||||
cirelation = Blueprint("cirelation", __name__)
|
||||
|
||||
|
||||
@cirelation.route("/types", methods=["GET"])
|
||||
def get_types():
|
||||
manager = CIRelationManager()
|
||||
return jsonify(relation_types=manager.relation_types)
|
||||
|
||||
|
||||
@cirelation.route("/<int:first_ci>/second_cis", methods=["GET"])
|
||||
def get_second_cis_by_first_ci(first_ci=None):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_per_page(request.values.get("count"))
|
||||
relation_type = request.values.get("relation_type", "contain")
|
||||
manager = CIRelationManager()
|
||||
numfound, total, second_cis = manager.get_second_cis(
|
||||
first_ci, page=page, per_page=count, relation_type=relation_type)
|
||||
return jsonify(numfound=numfound, total=total,
|
||||
page=page, second_cis=second_cis)
|
||||
|
||||
|
||||
@cirelation.route("/<int:second_ci>/first_cis", methods=["GET"])
|
||||
def get_first_cis_by_second_ci(second_ci=None):
|
||||
page = get_page(request.values.get("page", 1))
|
||||
count = get_per_page(request.values.get("count"))
|
||||
relation_type = request.values.get("relation_type", "contain")
|
||||
|
||||
manager = CIRelationManager()
|
||||
numfound, total, first_cis = manager.get_first_cis(
|
||||
second_ci, per_page=count, page=page, relation_type=relation_type)
|
||||
return jsonify(numfound=numfound, total=total,
|
||||
page=page, first_cis=first_cis)
|
||||
|
||||
|
||||
@cirelation.route("/<int:first_ci>/<int:second_ci>", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_ci_relation(first_ci=None, second_ci=None):
|
||||
relation_type = request.values.get("relation_type", "contain")
|
||||
manager = CIRelationManager()
|
||||
res = manager.add(first_ci, second_ci, relation_type=relation_type)
|
||||
return jsonify(cr_id=res)
|
||||
|
||||
|
||||
@cirelation.route("/<int:cr_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_ci_relation(cr_id=None):
|
||||
manager = CIRelationManager()
|
||||
manager.delete(cr_id)
|
||||
return jsonify(message="CIType Relation is deleted")
|
||||
|
||||
|
||||
@cirelation.route("/<int:first_ci>/<int:second_ci>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_ci_relation_2(first_ci, second_ci):
|
||||
manager = CIRelationManager()
|
||||
manager.delete_2(first_ci, second_ci)
|
||||
return jsonify(message="CIType Relation is deleted")
|
|
@ -1,89 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import jsonify
|
||||
from flask import request
|
||||
from flask import abort
|
||||
|
||||
from lib.ci_type import CITypeManager
|
||||
from lib.decorator import argument_required
|
||||
from lib.auth import auth_with_key
|
||||
|
||||
|
||||
citype = Blueprint("citype", __name__)
|
||||
|
||||
|
||||
@citype.route("", methods=["GET"])
|
||||
def get_citypes():
|
||||
type_name = request.args.get("type_name")
|
||||
manager = CITypeManager()
|
||||
citypes = manager.get_citypes(type_name)
|
||||
count = len(citypes)
|
||||
return jsonify(numfound=count, citypes=citypes)
|
||||
|
||||
|
||||
@citype.route("/query", methods=["GET"])
|
||||
def query():
|
||||
with argument_required("type"):
|
||||
_type = request.args.get("type")
|
||||
manager = CITypeManager()
|
||||
res = manager.query(_type)
|
||||
return jsonify(citype=res)
|
||||
|
||||
|
||||
@citype.route("", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_citype():
|
||||
with argument_required("type_name"):
|
||||
type_name = request.values.get("type_name")
|
||||
type_alias = request.values.get("type_alias")
|
||||
if type_alias is None:
|
||||
type_alias = type_name
|
||||
_id = request.values.get("_id")
|
||||
unique = request.values.get("unique")
|
||||
enabled = request.values.get("enabled", True)
|
||||
icon_url = request.values.get("icon_url", "")
|
||||
manager = CITypeManager()
|
||||
ret, res = manager.add(type_name, type_alias, _id=_id,
|
||||
unique=unique, enabled=enabled,
|
||||
icon_url=icon_url)
|
||||
if ret:
|
||||
return jsonify(type_id=res)
|
||||
abort(500, res)
|
||||
|
||||
|
||||
@citype.route("/<int:type_id>", methods=["PUT"])
|
||||
@auth_with_key
|
||||
def update_citype(type_id=None):
|
||||
type_name = request.values.get("type_name")
|
||||
type_alias = request.values.get("type_alias")
|
||||
_id = request.values.get("_id")
|
||||
unique = request.values.get("unique")
|
||||
icon_url = request.values.get("icon_url")
|
||||
enabled = request.values.get("enabled")
|
||||
enabled = False if enabled in (0, "0") else True \
|
||||
if enabled is not None else None
|
||||
manager = CITypeManager()
|
||||
ret, res = manager.update(type_id, type_name, type_alias, _id=_id,
|
||||
unique=unique, icon_url=icon_url,
|
||||
enabled=enabled)
|
||||
if ret:
|
||||
return jsonify(type_id=type_id)
|
||||
abort(500, res)
|
||||
|
||||
|
||||
@citype.route("/<int:type_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_citype(type_id=None):
|
||||
manager = CITypeManager()
|
||||
res = manager.delete(type_id)
|
||||
return jsonify(message=res)
|
||||
|
||||
|
||||
@citype.route("/enable/<int:type_id>", methods=["GET", "POST"])
|
||||
def enable(type_id=None):
|
||||
enable = request.values.get("enable", True)
|
||||
manager = CITypeManager()
|
||||
manager.set_enabled(type_id, enabled=enable)
|
||||
return jsonify(type_id=type_id)
|
|
@ -1,55 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import jsonify
|
||||
from flask import request
|
||||
|
||||
from lib.ci_type import CITypeRelationManager
|
||||
from lib.auth import auth_with_key
|
||||
|
||||
|
||||
cityperelation = Blueprint("cityperelation", __name__)
|
||||
|
||||
|
||||
@cityperelation.route("/types", methods=["GET"])
|
||||
def get_types():
|
||||
manager = CITypeRelationManager()
|
||||
return jsonify(relation_types=manager.relation_types)
|
||||
|
||||
|
||||
@cityperelation.route("/<int:parent>/children", methods=["GET"])
|
||||
def get_children_by_parent(parent=None):
|
||||
manager = CITypeRelationManager()
|
||||
return jsonify(children=manager.get_children(parent))
|
||||
|
||||
|
||||
@cityperelation.route("/<int:child>/parents", methods=["GET"])
|
||||
def get_parents_by_child(child=None):
|
||||
manager = CITypeRelationManager()
|
||||
return jsonify(parents=manager.get_parents(child))
|
||||
|
||||
|
||||
@cityperelation.route("/<int:parent>/<int:child>", methods=["POST"])
|
||||
@auth_with_key
|
||||
def create_citype_realtions(parent=None, child=None):
|
||||
relation_type = request.values.get("relation_type", "contain")
|
||||
manager = CITypeRelationManager()
|
||||
res = manager.add(parent, child, relation_type=relation_type)
|
||||
return jsonify(ctr_id=res)
|
||||
|
||||
|
||||
@cityperelation.route("/<int:ctr_id>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_citype_relation(ctr_id=None):
|
||||
manager = CITypeRelationManager()
|
||||
manager.delete(ctr_id)
|
||||
return jsonify(message="CIType Relation is deleted")
|
||||
|
||||
|
||||
@cityperelation.route("/<int:parent>/<int:child>", methods=["DELETE"])
|
||||
@auth_with_key
|
||||
def delete_citype_relation_2(parent=None, child=None):
|
||||
manager = CITypeRelationManager()
|
||||
manager.delete_2(parent, child)
|
||||
return jsonify(message="CIType Relation is deleted")
|
116
core/history.py
116
core/history.py
|
@ -1,116 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from flask import jsonify
|
||||
from flask import current_app
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
from flask import abort
|
||||
|
||||
from models.history import OperationRecord
|
||||
from models.history import CIRelationHistory
|
||||
from models.history import CIAttributeHistory
|
||||
from models.attribute import CIAttributeCache
|
||||
from extensions import db
|
||||
from models import row2dict
|
||||
from models.account import UserCache
|
||||
from lib.ci import CIManager
|
||||
from lib.utils import get_page
|
||||
|
||||
history = Blueprint("history", __name__)
|
||||
|
||||
|
||||
@history.route("/record", methods=["GET"])
|
||||
def get_record():
|
||||
page = get_page(request.values.get("page", 1))
|
||||
_start = request.values.get("start")
|
||||
_end = request.values.get("end")
|
||||
username = request.values.get("username", "")
|
||||
per_page_cnt = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
start, end = None, None
|
||||
if _start:
|
||||
try:
|
||||
start = datetime.datetime.strptime(_start, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
abort(400, 'incorrect start date time')
|
||||
if _end:
|
||||
try:
|
||||
end = datetime.datetime.strptime(_end, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
abort(400, 'incorrect end date time')
|
||||
records = db.session.query(OperationRecord)
|
||||
numfound = db.session.query(db.func.count(OperationRecord.record_id))
|
||||
if start:
|
||||
records = records.filter(OperationRecord.timestamp >= start)
|
||||
numfound = numfound.filter(OperationRecord.timestamp >= start)
|
||||
if end:
|
||||
records = records.filter(OperationRecord.timestamp <= end)
|
||||
numfound = records.filter(OperationRecord.timestamp <= end)
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user:
|
||||
records = records.filter(OperationRecord.uid == user.uid)
|
||||
else:
|
||||
return jsonify(numfound=0, records=[],
|
||||
page=1, total=0, start=_start,
|
||||
end=_end, username=username)
|
||||
records = records.order_by(-OperationRecord.record_id).offset(
|
||||
per_page_cnt * (page - 1)).limit(per_page_cnt).all()
|
||||
total = len(records)
|
||||
numfound = numfound.first()[0]
|
||||
res = []
|
||||
for record in records:
|
||||
_res = row2dict(record)
|
||||
_res["user"] = UserCache.get(_res.get("uid")).nickname \
|
||||
if UserCache.get(_res.get("uid")).nickname \
|
||||
else UserCache.get(_res.get("uid")).username
|
||||
attr_history = db.session.query(CIAttributeHistory.attr_id).filter(
|
||||
CIAttributeHistory.record_id == _res.get("record_id")).all()
|
||||
_res["attr_history"] = [CIAttributeCache.get(h.attr_id).attr_alias
|
||||
for h in attr_history]
|
||||
rel_history = db.session.query(CIRelationHistory.operate_type).filter(
|
||||
CIRelationHistory.record_id == _res.get("record_id")).all()
|
||||
rel_statis = {}
|
||||
for rel in rel_history:
|
||||
if rel.operate_type not in rel_statis:
|
||||
rel_statis[rel.operate_type] = 1
|
||||
else:
|
||||
rel_statis[rel.res.operate_type] += 1
|
||||
_res["rel_history"] = rel_statis
|
||||
res.append(_res)
|
||||
|
||||
return jsonify(numfound=numfound, records=res, page=page, total=total,
|
||||
start=_start, end=_end, username=username)
|
||||
|
||||
|
||||
@history.route("/<int:record_id>", methods=["GET"])
|
||||
def get_detail_by_record(record_id=None):
|
||||
record = db.session.query(OperationRecord).filter(
|
||||
OperationRecord.record_id == record_id).first()
|
||||
if record is None:
|
||||
abort(404, "record is not found")
|
||||
username = UserCache.get(record.uid).nickname \
|
||||
if UserCache.get(record.uid).nickname \
|
||||
else UserCache.get(record.uid).username
|
||||
timestamp = record.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
attr_history = db.session.query(CIAttributeHistory).filter(
|
||||
CIAttributeHistory.record_id == record_id).all()
|
||||
rel_history = db.session.query(CIRelationHistory).filter(
|
||||
CIRelationHistory.record_id == record_id).all()
|
||||
attr_dict, rel_dict = dict(), {"add": [], "delete": []}
|
||||
for attr_h in attr_history:
|
||||
attr_dict[CIAttributeCache.get(attr_h.attr_id).attr_alias] = {
|
||||
"old": attr_h.old, "new": attr_h.new,
|
||||
"operate_type": attr_h.operate_type}
|
||||
manager = CIManager()
|
||||
for rel_h in rel_history:
|
||||
_, first = manager.get_ci_by_id(rel_h.first_ci_id)
|
||||
_, second = manager.get_ci_by_id(rel_h.second_ci_id)
|
||||
rel_dict[rel_h.operate_type].append(
|
||||
(first, rel_h.relation_type, second))
|
||||
|
||||
return jsonify(username=username, timestamp=timestamp,
|
||||
attr_history=attr_dict,
|
||||
rel_history=rel_dict)
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
|
||||
statis = Blueprint("statis", __name__)
|
||||
|
||||
|
||||
@statis.route("")
|
||||
def statis():
|
||||
pass
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from flask.ext.mail import Mail
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask.ext.cache import Cache
|
||||
from flask.ext.celery import Celery
|
||||
|
||||
from lib.utils import RedisHandler
|
||||
|
||||
|
||||
__all__ = ['mail', 'db', 'cache', 'celery', "rd"]
|
||||
|
||||
|
||||
mail = Mail()
|
||||
db = SQLAlchemy()
|
||||
cache = Cache()
|
||||
celery = Celery()
|
||||
rd = RedisHandler()
|
|
@ -1,72 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask_script import Command, Option
|
||||
|
||||
|
||||
class GunicornServer(Command):
|
||||
description = 'Run the app within Gunicorn'
|
||||
|
||||
def __init__(self, host='127.0.0.1', port=5000, workers=8,
|
||||
worker_class="sync", daemon=False):
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.workers = workers
|
||||
self.worker_class = worker_class
|
||||
self.daemon = daemon
|
||||
|
||||
def get_options(self):
|
||||
return (
|
||||
Option('-H', '--host',
|
||||
dest='host',
|
||||
default=self.host),
|
||||
|
||||
Option('-p', '--port',
|
||||
dest='port',
|
||||
type=int,
|
||||
default=self.port),
|
||||
|
||||
Option('-w', '--workers',
|
||||
dest='workers',
|
||||
type=int,
|
||||
default=self.workers),
|
||||
|
||||
Option("-c", "--worker_class",
|
||||
dest='worker_class',
|
||||
type=str,
|
||||
default=self.worker_class),
|
||||
|
||||
Option("-d", "--daemon",
|
||||
dest="daemon",
|
||||
type=bool,
|
||||
default=self.daemon)
|
||||
)
|
||||
|
||||
def handle(self, app, host, port, workers, worker_class, daemon):
|
||||
|
||||
from gunicorn import version_info
|
||||
|
||||
if version_info < (0, 9, 0):
|
||||
from gunicorn.arbiter import Arbiter
|
||||
from gunicorn.config import Config
|
||||
|
||||
arbiter = Arbiter(Config({'bind': "%s:%d" % (host, int(port)),
|
||||
'workers': workers,
|
||||
'worker_class': worker_class,
|
||||
'daemon': daemon}), app)
|
||||
arbiter.run()
|
||||
else:
|
||||
from gunicorn.app.base import Application
|
||||
|
||||
class FlaskApplication(Application):
|
||||
def init(self, parser, opts, args):
|
||||
return {
|
||||
'bind': '{0}:{1}'.format(host, port),
|
||||
'workers': workers,
|
||||
'worker_class': worker_class,
|
||||
'daemon': daemon
|
||||
}
|
||||
|
||||
def load(self):
|
||||
return app
|
||||
|
||||
FlaskApplication().run()
|
145
lib/account.py
145
lib/account.py
|
@ -1,145 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import uuid
|
||||
import random
|
||||
import string
|
||||
import datetime
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
|
||||
from extensions import db
|
||||
from models.account import UserCache
|
||||
from models.account import User
|
||||
from models.account import UserRole
|
||||
|
||||
|
||||
class AccountManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_user_by_uid(self, uid):
|
||||
user = UserCache.get(uid)
|
||||
return user
|
||||
|
||||
def _generate_key(self):
|
||||
key = uuid.uuid4().hex
|
||||
secret = ''.join(random.sample(string.ascii_letters +
|
||||
string.digits + '~!@#$%^&*?', 32))
|
||||
return key, secret
|
||||
|
||||
def validate(self, username, password):
|
||||
user, authenticated = User.query.authenticate(username, password)
|
||||
return user, authenticated
|
||||
|
||||
def create_user(self, **kwargs):
|
||||
username = kwargs.get("username")
|
||||
if username:
|
||||
user = UserCache.get(username)
|
||||
if user is not None:
|
||||
user, authenticated = self.validate(
|
||||
username, kwargs.get("password"))
|
||||
if authenticated:
|
||||
return user
|
||||
else:
|
||||
return abort(401, "authenticate validate failed")
|
||||
else:
|
||||
return abort(400, "argument username is required")
|
||||
user = User()
|
||||
email = kwargs.get("email", "")
|
||||
if not email:
|
||||
return abort(400, "argument email is required")
|
||||
user.email = email
|
||||
user.password = kwargs.get("password")
|
||||
user.username = kwargs.get("username", "")
|
||||
user.nickname = kwargs.get("nickname") if kwargs.get("nickname") \
|
||||
else kwargs.get("username", "")
|
||||
key, secret = self._generate_key()
|
||||
user.key = key
|
||||
user.secret = secret
|
||||
user.date_joined = datetime.datetime.now()
|
||||
user.block = 0
|
||||
|
||||
db.session.add(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("create user is error {0}".format(str(e)))
|
||||
return abort(500, "create user is error, {0}".format(str(e)))
|
||||
return user
|
||||
|
||||
def update_user(self, uid, **kwargs):
|
||||
user = UserCache.get(uid)
|
||||
if user is None:
|
||||
return abort(400, "the user[{0}] is not existed".format(uid))
|
||||
user.username = kwargs.get("username", "") \
|
||||
if kwargs.get("username") else user.username
|
||||
user.nickname = kwargs.get("nickname") \
|
||||
if kwargs.get("nickname") else user.nickname
|
||||
user.department = kwargs.get("department") \
|
||||
if kwargs.get("department") else user.department
|
||||
user.catalog = kwargs.get("catalog") \
|
||||
if kwargs.get("catalog") else user.catalog
|
||||
user.email = kwargs.get("email") \
|
||||
if kwargs.get("email") else user.email
|
||||
user.mobile = kwargs.get("mobile") \
|
||||
if kwargs.get("mobile") else user.mobile
|
||||
db.session.add(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("create user is error {0}".format(str(e)))
|
||||
return abort(500, "create user is error, {0}".format(str(e)))
|
||||
return True, user
|
||||
|
||||
def delete_user(self, uid):
|
||||
user = UserCache.get(uid)
|
||||
if user is None:
|
||||
return abort(400, "the user[{0}] is not existed".format(uid))
|
||||
db.session.query(UserRole).filter(UserRole.uid == uid).delete()
|
||||
db.session.delete(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("delete user error, {0}".format(str(e)))
|
||||
return abort(500, "delete user error, {0}".format(str(e)))
|
||||
return True, uid
|
||||
|
||||
def update_password(self, uid, old, new, confirm):
|
||||
user = User.query.get(uid)
|
||||
if not user:
|
||||
return abort(400, "user is not existed")
|
||||
if not user.check_password(old):
|
||||
return abort(400, "invalidate old password")
|
||||
if not (new and confirm and new == confirm):
|
||||
return abort(400, """Password cannot be empty,
|
||||
two inputs must be the same""")
|
||||
user.password = new
|
||||
db.session.add(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("set password error, %s" % str(e))
|
||||
return abort(500, "set password errors, {0:s}".format(str(e)))
|
||||
return True, user
|
||||
|
||||
def reset_key(self, uid):
|
||||
user = UserCache.get(uid)
|
||||
if user is None:
|
||||
return abort(400, "the user[{0}] is not existed".format(uid))
|
||||
key, secret = self._generate_key()
|
||||
user.key = key
|
||||
user.secret = secret
|
||||
db.session.add(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("reset key is error, {0}".format(str(e)))
|
||||
return abort(500, "reset key is error, {0}".format(str(e)))
|
||||
return True, user
|
168
lib/attribute.py
168
lib/attribute.py
|
@ -1,168 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
|
||||
from extensions import db
|
||||
from models.attribute import CIAttribute
|
||||
from models.attribute import CIAttributeCache
|
||||
from models import row2dict
|
||||
from lib.const import type_map
|
||||
|
||||
|
||||
class AttributeManager(object):
|
||||
"""
|
||||
CI attributes manager
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _get_choice_value(self, attr_id, value_type):
|
||||
_table = type_map.get("choice").get(value_type)
|
||||
choice_values = db.session.query(_table.value).filter(
|
||||
_table.attr_id == attr_id).all()
|
||||
return [choice_value.value for choice_value in choice_values]
|
||||
|
||||
def _add_choice_value(self, choice_value, attr_id, value_type):
|
||||
_table = type_map.get("choice").get(value_type)
|
||||
db.session.query(_table).filter(_table.attr_id == attr_id).delete()
|
||||
db.session.flush()
|
||||
for v in choice_value.strip().split(","):
|
||||
table = _table()
|
||||
table.attr_id = attr_id
|
||||
table.value = v
|
||||
db.session.add(table)
|
||||
db.session.flush()
|
||||
|
||||
def get_attributes(self, name=None):
|
||||
"""
|
||||
return attribute by name,
|
||||
if name is None, then return all attributes
|
||||
"""
|
||||
attrs = db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_name.ilike("%{0}%".format(name))).all() \
|
||||
if name is not None else db.session.query(CIAttribute).all()
|
||||
res = list()
|
||||
for attr in attrs:
|
||||
attr_dict = row2dict(attr)
|
||||
if attr.is_choice:
|
||||
attr_dict["choice_value"] = self._get_choice_value(
|
||||
attr.attr_id, attr.value_type)
|
||||
res.append(attr_dict)
|
||||
return res
|
||||
|
||||
def get_attribute_by_name(self, attr_name):
|
||||
attr = db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_name == attr_name).first()
|
||||
if attr:
|
||||
attr_dict = row2dict(attr)
|
||||
if attr.is_choice:
|
||||
attr_dict["choice_value"] = self._get_choice_value(
|
||||
attr.attr_id, attr.value_type)
|
||||
return attr_dict
|
||||
|
||||
def get_attribute_by_alias(self, attr_alias):
|
||||
attr = db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_alias == attr_alias).first()
|
||||
if attr:
|
||||
attr_dict = row2dict(attr)
|
||||
if attr.is_choice:
|
||||
attr_dict["choice_value"] = self._get_choice_value(
|
||||
attr.attr_id, attr.value_type)
|
||||
return attr_dict
|
||||
|
||||
def get_attribute_by_id(self, attr_id):
|
||||
attr = db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_id == attr_id).first()
|
||||
if attr:
|
||||
attr_dict = row2dict(attr)
|
||||
if attr.is_choice:
|
||||
attr_dict["choice_value"] = self._get_choice_value(
|
||||
attr.attr_id, attr.value_type)
|
||||
return attr_dict
|
||||
|
||||
def add(self, attr_name, attr_alias, **kwargs):
|
||||
choice_value = kwargs.get("choice_value", False)
|
||||
attr = CIAttributeCache.get(attr_name)
|
||||
if attr is not None:
|
||||
return False, "attribute {0} is already existed".format(attr_name)
|
||||
is_choice = False
|
||||
if choice_value:
|
||||
is_choice = True
|
||||
if not attr_alias:
|
||||
attr_alias = attr_name
|
||||
attr = CIAttribute()
|
||||
attr.attr_name = attr_name
|
||||
attr.attr_alias = attr_alias
|
||||
attr.is_choice = is_choice
|
||||
attr.is_multivalue = kwargs.get("is_multivalue", False)
|
||||
attr.is_uniq = kwargs.get("is_uniq", False)
|
||||
attr.is_index = kwargs.get("is_index", False)
|
||||
attr.value_type = kwargs.get("value_type", "text")
|
||||
db.session.add(attr)
|
||||
db.session.flush()
|
||||
|
||||
if choice_value:
|
||||
self._add_choice_value(choice_value, attr.attr_id, attr.value_type)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add attribute error, {0}".format(str(e)))
|
||||
return False, str(e)
|
||||
CIAttributeCache.clean(attr)
|
||||
return True, attr.attr_id
|
||||
|
||||
def update(self, attr_id, *args, **kwargs):
|
||||
attr = db.session.query(CIAttribute).filter_by(attr_id=attr_id).first()
|
||||
if not attr:
|
||||
return False, "CI attribute you want to update is not existed"
|
||||
choice_value = kwargs.get("choice_value", False)
|
||||
is_choice = False
|
||||
if choice_value:
|
||||
is_choice = True
|
||||
attr.attr_name = args[0]
|
||||
attr.attr_alias = args[1]
|
||||
if not args[1]:
|
||||
attr.attr_alias = args[0]
|
||||
attr.is_choice = is_choice
|
||||
attr.is_multivalue = kwargs.get("is_multivalue", False)
|
||||
attr.is_uniq = kwargs.get("is_uniq", False)
|
||||
attr.value_type = kwargs.get("value_type", "text")
|
||||
db.session.add(attr)
|
||||
db.session.flush()
|
||||
if is_choice:
|
||||
self._add_choice_value(choice_value, attr.attr_id, attr.value_type)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("update attribute error, {0}".format(
|
||||
str(e)))
|
||||
return False, str(e)
|
||||
CIAttributeCache.clean(attr)
|
||||
return True, attr.attr_id
|
||||
|
||||
def delete(self, attr_id):
|
||||
attr, name = db.session.query(CIAttribute).filter_by(
|
||||
attr_id=attr_id).first(), None
|
||||
if attr:
|
||||
if attr.is_choice:
|
||||
choice_table = type_map["choice"].get(attr.value_type)
|
||||
db.session.query(choice_table).filter(
|
||||
choice_table.attr_id == attr_id).delete()
|
||||
db.session.flush()
|
||||
name = attr.attr_name
|
||||
CIAttributeCache.clean(attr)
|
||||
db.session.delete(attr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("delete attribute error, {0}".format(
|
||||
str(e)))
|
||||
return abort(500, str(e))
|
||||
else:
|
||||
return abort(404, "attribute you want to delete is not existed")
|
||||
return name
|
64
lib/auth.py
64
lib/auth.py
|
@ -1,64 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import urllib
|
||||
from functools import wraps
|
||||
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import abort
|
||||
from flask.ext.principal import identity_changed
|
||||
from flask.ext.principal import Identity
|
||||
from flask.ext.principal import AnonymousIdentity
|
||||
|
||||
from models.account import User
|
||||
from models.account import UserCache
|
||||
|
||||
|
||||
def auth_with_key(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
ip = request.remote_addr
|
||||
if request.data:
|
||||
request_args = dict()
|
||||
_args = request.data.split("&")
|
||||
for arg in _args:
|
||||
if arg:
|
||||
request_args[arg.split("=")[0]] = \
|
||||
urllib.unquote(arg.split("=")[1])
|
||||
else:
|
||||
request_args = request.values
|
||||
|
||||
key = request_args.get('_key')
|
||||
secret = request_args.get('_secret')
|
||||
if not key and not secret and \
|
||||
ip.strip() in current_app.config.get("WHITE_LIST"):
|
||||
ip = ip.strip()
|
||||
user = UserCache.get(ip)
|
||||
if user:
|
||||
identity_changed.send(current_app._get_current_object(),
|
||||
identity=Identity(user.uid))
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
identity_changed.send(current_app._get_current_object(),
|
||||
identity=AnonymousIdentity())
|
||||
return abort(401, "invalid _key and _secret")
|
||||
|
||||
path = request.path
|
||||
|
||||
keys = sorted(request_args.keys())
|
||||
req_args = [request_args[k] for k in keys
|
||||
if str(k) not in ("_key", "_secret")]
|
||||
current_app.logger.debug('args is %s' % req_args)
|
||||
user, authenticated = User.query.authenticate_with_key(
|
||||
key, secret, req_args, path)
|
||||
if user and authenticated:
|
||||
identity_changed.send(current_app._get_current_object(),
|
||||
identity=Identity(user.get("uid")))
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
identity_changed.send(current_app._get_current_object(),
|
||||
identity=AnonymousIdentity())
|
||||
return abort(401, "invalid _key and _secret")
|
||||
|
||||
return wrapper
|
704
lib/ci.py
704
lib/ci.py
|
@ -1,704 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import uuid
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
from sqlalchemy import or_
|
||||
|
||||
from extensions import db
|
||||
from extensions import rd
|
||||
from models.ci import CI
|
||||
from models.ci_relation import CIRelation
|
||||
from models.ci_type import CITypeAttribute
|
||||
from models.ci_type import CITypeCache
|
||||
from models.ci_type import CITypeSpecCache
|
||||
from models.history import CIAttributeHistory
|
||||
from models.attribute import CIAttributeCache
|
||||
from lib.const import TableMap
|
||||
from lib.const import type_map
|
||||
from lib.value import AttributeValueManager
|
||||
from lib.history import CIAttributeHistoryManger
|
||||
from lib.history import CIRelationHistoryManager
|
||||
from lib.query_sql import QUERY_HOSTS_NUM_BY_PRODUCT
|
||||
from lib.query_sql import QUERY_HOSTS_NUM_BY_BU
|
||||
from lib.query_sql import QUERY_HOSTS_NUM_BY_PROJECT
|
||||
from lib.query_sql import QUERY_CIS_BY_IDS
|
||||
from lib.query_sql import QUERY_CIS_BY_VALUE_TABLE
|
||||
from tasks.cmdb import ci_cache
|
||||
from tasks.cmdb import ci_delete
|
||||
|
||||
|
||||
class CIManager(object):
|
||||
""" manage CI interface
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_ci_by_id(self, ci_id, ret_key="name",
|
||||
fields=None, need_children=True, use_master=False):
|
||||
"""@params: `ret_key` is one of 'name', 'id', 'alias'
|
||||
`fields` is list of attribute name/alias/id
|
||||
"""
|
||||
ci = CI.query.get(ci_id) or \
|
||||
abort(404, "CI {0} is not existed".format(ci_id))
|
||||
|
||||
res = dict()
|
||||
|
||||
if need_children:
|
||||
children = self.get_children(ci_id, ret_key=ret_key) # one floor
|
||||
res.update(children)
|
||||
ci_type = CITypeCache.get(ci.type_id)
|
||||
res["ci_type"] = ci_type.type_name
|
||||
uniq_key = CIAttributeCache.get(ci_type.uniq_id)
|
||||
if not fields: # fields are all attributes
|
||||
attr_ids = db.session.query(CITypeAttribute.attr_id).filter_by(
|
||||
type_id=ci.type_id)
|
||||
fields = [CIAttributeCache.get(_.attr_id).attr_name
|
||||
for _ in attr_ids]
|
||||
|
||||
if uniq_key.attr_name not in fields:
|
||||
fields.append(uniq_key.attr_name)
|
||||
if fields:
|
||||
value_manager = AttributeValueManager()
|
||||
_res = value_manager._get_attr_values(
|
||||
fields, ci_id,
|
||||
ret_key=ret_key, uniq_key=uniq_key, use_master=use_master)
|
||||
res.update(_res)
|
||||
res['_type'] = ci_type.type_id
|
||||
res['_id'] = ci_id
|
||||
return res
|
||||
|
||||
def get_ci_by_ids(self, ci_id_list, ret_key="name", fields=None):
|
||||
result = list()
|
||||
for ci_id in ci_id_list:
|
||||
res = self.get_ci_by_id(ci_id, ret_key=ret_key, fields=fields)
|
||||
result.append(res)
|
||||
return result
|
||||
|
||||
def get_children(self, ci_id, ret_key='name', relation_type="contain"):
|
||||
second_cis = db.session.query(CIRelation.second_ci_id).filter(
|
||||
CIRelation.first_ci_id == ci_id).filter(or_(
|
||||
CIRelation.relation_type == relation_type,
|
||||
CIRelation.relation_type == "deploy"))
|
||||
second_ci_ids = (second_ci.second_ci_id for second_ci in second_cis)
|
||||
ci_types = {}
|
||||
for ci_id in second_ci_ids:
|
||||
type_id = db.session.query(CI.type_id).filter(
|
||||
CI.ci_id == ci_id).first().type_id
|
||||
if type_id not in ci_types:
|
||||
ci_types[type_id] = [ci_id]
|
||||
else:
|
||||
ci_types[type_id].append(ci_id)
|
||||
res = {}
|
||||
for type_id in ci_types:
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
children = get_cis_by_ids(map(str, ci_types.get(type_id)),
|
||||
ret_key=ret_key)
|
||||
res[ci_type.type_name] = children
|
||||
return res
|
||||
|
||||
def get_cis_by_type(self, type_id, ret_key="name", fields="",
|
||||
page=1, per_page=None):
|
||||
if per_page is None:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
cis = db.session.query(CI.ci_id).filter(CI.type_id == type_id)
|
||||
numfound = cis.count()
|
||||
cis = cis.offset((page - 1) * per_page).limit(per_page)
|
||||
res = list()
|
||||
ci_ids = [str(ci.ci_id) for ci in cis]
|
||||
if ci_ids:
|
||||
res = get_cis_by_ids(ci_ids, ret_key, fields)
|
||||
return numfound, page, res
|
||||
|
||||
def ci_is_exist(self, ci_type, unique_key, unique):
|
||||
table = TableMap(attr_name=unique_key.attr_name).table
|
||||
unique = db.session.query(table).filter(
|
||||
table.attr_id == unique_key.attr_id).filter(
|
||||
table.value == unique).first()
|
||||
if unique:
|
||||
return db.session.query(CI).filter(
|
||||
CI.ci_id == unique.ci_id).first()
|
||||
|
||||
def _delete_ci_by_id(self, ci_id):
|
||||
db.session.query(CI.ci_id).filter(CI.ci_id == ci_id).delete()
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("delete ci is error, {0}".format(str(e)))
|
||||
|
||||
def add(self, ci_type_name, exist_policy="replace",
|
||||
_no_attribute_policy="ignore", **ci_dict):
|
||||
ci_existed = False
|
||||
ci_type = CITypeCache.get(ci_type_name) or \
|
||||
abort(404, "CIType {0} is not existed".format(ci_type_name))
|
||||
|
||||
unique_key = CIAttributeCache.get(ci_type.uniq_id) \
|
||||
or abort(400, 'illegality unique attribute')
|
||||
|
||||
unique = ci_dict.get(unique_key.attr_name) \
|
||||
or abort(400, '{0} missing'.format(unique_key.attr_name))
|
||||
|
||||
old_ci = self.ci_is_exist(ci_type, unique_key, unique)
|
||||
if old_ci is not None:
|
||||
ci_existed = True
|
||||
if exist_policy == 'reject':
|
||||
return abort(400, 'CI is existed')
|
||||
if old_ci.type_id != ci_type.type_id: # update ci_type
|
||||
old_ci.type_id = ci_type.type_id
|
||||
db.session.add(old_ci)
|
||||
db.session.flush()
|
||||
ci = old_ci
|
||||
else:
|
||||
if exist_policy == 'need':
|
||||
return abort(404, 'CI {0} not exist'.format(unique))
|
||||
ci = CI()
|
||||
ci.type_id = ci_type.type_id
|
||||
_uuid = uuid.uuid4().hex
|
||||
ci.uuid = _uuid
|
||||
ci.created_time = datetime.datetime.now()
|
||||
db.session.add(ci)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error('add CI error: {0}'.format(str(e)))
|
||||
return abort(400, 'add CI error')
|
||||
value_manager = AttributeValueManager()
|
||||
histories = list()
|
||||
for p, v in ci_dict.items():
|
||||
ret, res = value_manager.add_attr_value(
|
||||
p, v, ci.ci_id, ci_type,
|
||||
_no_attribute_policy=_no_attribute_policy,
|
||||
ci_existed=ci_existed)
|
||||
if not ret:
|
||||
db.session.rollback()
|
||||
if not ci_existed:
|
||||
self.delete(ci.ci_id)
|
||||
current_app.logger.info(res)
|
||||
return abort(400, res)
|
||||
if res is not None:
|
||||
histories.append(res)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
current_app.logger.error(str(e))
|
||||
db.session.rollback()
|
||||
if not ci_existed: # only add
|
||||
self.delete(ci.ci_id)
|
||||
return abort(400, "add CI error")
|
||||
his_manager = CIAttributeHistoryManger()
|
||||
his_manager.add(ci.ci_id, histories)
|
||||
ci_cache.apply_async([ci.ci_id], queue="cmdb_async")
|
||||
return ci.ci_id
|
||||
|
||||
def update_unique_value(self, ci_id, args):
|
||||
ci = self.get_ci_by_id(ci_id, need_children=False)
|
||||
unique_key = ci.get("unique")
|
||||
attr = CIAttributeCache.get(unique_key)
|
||||
table_key = "index_{0}".format(attr.value_type) \
|
||||
if attr.is_index else attr.value_type
|
||||
value_table = type_map.get("table").get(table_key)
|
||||
v = args.get(unique_key)
|
||||
if value_table and v:
|
||||
item = db.session.query(value_table).filter(
|
||||
value_table.ci_id == ci_id).filter(
|
||||
value_table.attr_id == attr.attr_id).first()
|
||||
if item:
|
||||
converter = type_map.get("converter").get(attr.value_type)
|
||||
try:
|
||||
item.value = converter(v)
|
||||
except:
|
||||
return abort(400, "value is illegal")
|
||||
db.session.add(item)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(str(e))
|
||||
return abort(400, "update unique failed")
|
||||
ci_cache.apply_async([ci_id], queue="cmdb_async")
|
||||
|
||||
def delete(self, ci_id):
|
||||
ci = db.session.query(CI).filter(CI.ci_id == ci_id).first()
|
||||
if ci is not None:
|
||||
attrs = db.session.query(CITypeAttribute.attr_id).filter(
|
||||
CITypeAttribute.type_id == ci.type_id).all()
|
||||
attr_names = []
|
||||
for attr in attrs:
|
||||
attr_names.append(CIAttributeCache.get(attr.attr_id).attr_name)
|
||||
attr_names = set(attr_names)
|
||||
for attr_name in attr_names:
|
||||
Table = TableMap(attr_name=attr_name).table
|
||||
db.session.query(Table).filter(Table.ci_id == ci_id).delete()
|
||||
db.session.query(CIRelation).filter(
|
||||
CIRelation.first_ci_id == ci_id).delete()
|
||||
db.session.query(CIRelation).filter(
|
||||
CIRelation.second_ci_id == ci_id).delete()
|
||||
# db.session.query(CIAttributeHistory).filter(
|
||||
# CIAttributeHistory.ci_id == ci_id).delete()
|
||||
|
||||
db.session.flush()
|
||||
db.session.delete(ci)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("delete CI error, {0}".format(str(e)))
|
||||
return abort(400, "delete CI error, {0}".format(str(e)))
|
||||
# todo: write history
|
||||
ci_delete.apply_async([ci.ci_id], queue="cmdb_async")
|
||||
return ci_id
|
||||
return abort(404, "CI {0} not found".format(ci_id))
|
||||
|
||||
def add_heartbeat(self, ci_type, unique):
|
||||
ci_type = CITypeCache.get(ci_type)
|
||||
if not ci_type:
|
||||
return 'error'
|
||||
uniq_key = CIAttributeCache.get(ci_type.uniq_id)
|
||||
Table = TableMap(attr_name=uniq_key.attr_name).table
|
||||
ci_id = db.session.query(Table.ci_id).filter(
|
||||
Table.attr_id == uniq_key.attr_id).filter(
|
||||
Table.value == unique).first()
|
||||
if ci_id is None:
|
||||
return 'error'
|
||||
ci = db.session.query(CI).filter(CI.ci_id == ci_id.ci_id).first()
|
||||
if ci is None:
|
||||
return 'error'
|
||||
|
||||
ci.heartbeat = datetime.datetime.now()
|
||||
|
||||
db.session.add(ci)
|
||||
db.session.commit()
|
||||
return "ok"
|
||||
|
||||
def get_heartbeat(self, page, type_id, agent_status=None):
|
||||
query = db.session.query(CI.ci_id, CI.heartbeat)
|
||||
expire = datetime.datetime.now() - datetime.timedelta(minutes=72)
|
||||
if type_id:
|
||||
query = query.filter(CI.type_id == type_id)
|
||||
else:
|
||||
query = query.filter(db.or_(CI.type_id == 7, CI.type_id == 8))
|
||||
if agent_status == -1:
|
||||
query = query.filter(CI.heartbeat == None)
|
||||
elif agent_status == 0:
|
||||
query = query.filter(CI.heartbeat <= expire)
|
||||
elif agent_status == 1:
|
||||
query = query.filter(CI.heartbeat > expire)
|
||||
numfound = query.count()
|
||||
per_page_count = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
cis = query.offset((page - 1) * per_page_count).limit(
|
||||
per_page_count).all()
|
||||
ci_ids = [ci.ci_id for ci in cis]
|
||||
heartbeat_dict = {}
|
||||
for ci in cis:
|
||||
if agent_status is not None:
|
||||
heartbeat_dict[ci.ci_id] = agent_status
|
||||
else:
|
||||
if ci.heartbeat is None:
|
||||
heartbeat_dict[ci.ci_id] = -1
|
||||
elif ci.heartbeat <= expire:
|
||||
heartbeat_dict[ci.ci_id] = 0
|
||||
else:
|
||||
heartbeat_dict[ci.ci_id] = 1
|
||||
current_app.logger.debug(heartbeat_dict)
|
||||
ci_ids = map(str, ci_ids)
|
||||
res = get_cis_by_ids(ci_ids, fields=["hostname", "private_ip"])
|
||||
result = [(i.get("hostname"), i.get("private_ip")[0], i.get("ci_type"),
|
||||
heartbeat_dict.get(i.get("_id"))) for i in res
|
||||
if i.get("private_ip")]
|
||||
return numfound, result
|
||||
|
||||
|
||||
class CIRelationManager(object):
|
||||
"""
|
||||
manage relation between CIs
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def relation_types(self):
|
||||
""" all CIType relation types
|
||||
"""
|
||||
from lib.const import CI_RELATION_TYPES
|
||||
|
||||
return CI_RELATION_TYPES
|
||||
|
||||
def get_second_cis(self, first_ci, relation_type="contain",
|
||||
page=1, per_page=None, **kwargs):
|
||||
if per_page is None:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
second_cis = db.session.query(
|
||||
CI.ci_id).join(
|
||||
CIRelation, CIRelation.second_ci_id == CI.ci_id).filter(
|
||||
CIRelation.first_ci_id == first_ci).filter(
|
||||
CIRelation.relation_type == relation_type)
|
||||
if kwargs: # special for devices
|
||||
second_cis = self._query_wrap_for_device(second_cis, **kwargs)
|
||||
numfound = second_cis.count()
|
||||
second_cis = second_cis.offset(
|
||||
(page - 1) * per_page).limit(per_page).all()
|
||||
ci_ids = [str(son.ci_id) for son in second_cis]
|
||||
total = len(ci_ids)
|
||||
result = get_cis_by_ids(ci_ids)
|
||||
return numfound, total, result
|
||||
|
||||
def get_grandsons(self, ci_id, page=1, per_page=None, **kwargs):
|
||||
if per_page is None:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
children = db.session.query(CIRelation.second_ci_id).filter(
|
||||
CIRelation.first_ci_id == ci_id).subquery()
|
||||
grandsons = db.session.query(CIRelation.second_ci_id).join(
|
||||
children,
|
||||
children.c.second_ci_id == CIRelation.first_ci_id).subquery()
|
||||
grandsons = db.session.query(CI.ci_id).join(
|
||||
grandsons, grandsons.c.second_ci_id == CI.ci_id)
|
||||
if kwargs:
|
||||
grandsons = self._query_wrap_for_device(grandsons, **kwargs)
|
||||
|
||||
numfound = grandsons.count()
|
||||
grandsons = grandsons.offset(
|
||||
(page - 1) * per_page).limit(per_page).all()
|
||||
if not grandsons:
|
||||
return 0, 0, []
|
||||
ci_ids = [str(son.ci_id) for son in grandsons]
|
||||
total = len(ci_ids)
|
||||
result = get_cis_by_ids(ci_ids)
|
||||
|
||||
return numfound, total, result
|
||||
|
||||
def _sort_handler(self, sort_by, query_sql):
|
||||
|
||||
if sort_by.startswith("+"):
|
||||
sort_type = "asc"
|
||||
sort_by = sort_by[1:]
|
||||
elif sort_by.startswith("-"):
|
||||
sort_type = "desc"
|
||||
sort_by = sort_by[1:]
|
||||
else:
|
||||
sort_type = "asc"
|
||||
attr = CIAttributeCache.get(sort_by)
|
||||
if attr is None:
|
||||
return query_sql
|
||||
|
||||
attr_id = attr.attr_id
|
||||
Table = TableMap(attr_name=sort_by).table
|
||||
|
||||
CI_table = query_sql.subquery()
|
||||
query_sql = db.session.query(CI_table.c.ci_id, Table.value).join(
|
||||
Table, Table.ci_id == CI_table.c.ci_id).filter(
|
||||
Table.attr_id == attr_id).order_by(
|
||||
getattr(Table.value, sort_type)())
|
||||
|
||||
return query_sql
|
||||
|
||||
def _query_wrap_for_device(self, query_sql, **kwargs):
|
||||
_type = kwargs.pop("_type", False) or kwargs.pop("type", False) \
|
||||
or kwargs.pop("ci_type", False)
|
||||
if _type:
|
||||
ci_type = CITypeCache.get(_type)
|
||||
if ci_type is None:
|
||||
return
|
||||
query_sql = query_sql.filter(CI.type_id == ci_type.type_id)
|
||||
|
||||
for k, v in kwargs.iteritems():
|
||||
attr = CIAttributeCache.get(k)
|
||||
if attr is None:
|
||||
continue
|
||||
Table = TableMap(attr_name=k).table
|
||||
CI_table = query_sql.subquery()
|
||||
query_sql = db.session.query(CI_table.c.ci_id).join(
|
||||
Table, Table.ci_id == CI_table.c.ci_id).filter(
|
||||
Table.attr_id == attr.attr_id).filter(
|
||||
Table.value.ilike(v.replace("*", "%")))
|
||||
|
||||
current_app.logger.debug(query_sql)
|
||||
sort_by = kwargs.pop("sort", False)
|
||||
if sort_by:
|
||||
query_sql = self._sort_handler(sort_by, query_sql)
|
||||
return query_sql
|
||||
|
||||
def get_great_grandsons(self, ci_id, page=1, per_page=None, **kwargs):
|
||||
if per_page is None:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
|
||||
children = db.session.query(CIRelation.second_ci_id).filter(
|
||||
CIRelation.first_ci_id == ci_id).subquery()
|
||||
grandsons = db.session.query(CIRelation.second_ci_id).join(
|
||||
children,
|
||||
children.c.second_ci_id == CIRelation.first_ci_id).subquery()
|
||||
great_grandsons = db.session.query(CIRelation.second_ci_id).join(
|
||||
grandsons,
|
||||
grandsons.c.second_ci_id == CIRelation.first_ci_id).subquery()
|
||||
great_grandsons = db.session.query(CI.ci_id).join(
|
||||
great_grandsons, great_grandsons.c.second_ci_id == CI.ci_id)
|
||||
if kwargs:
|
||||
great_grandsons = self._query_wrap_for_device(
|
||||
great_grandsons, **kwargs)
|
||||
if great_grandsons is None:
|
||||
return 0, 0, []
|
||||
numfound = great_grandsons.count()
|
||||
great_grandsons = great_grandsons.offset(
|
||||
(page - 1) * per_page).limit(per_page).all()
|
||||
ci_ids = [str(son.ci_id) for son in great_grandsons]
|
||||
total = len(ci_ids)
|
||||
result = get_cis_by_ids(ci_ids)
|
||||
|
||||
return numfound, total, result
|
||||
|
||||
def get_first_cis(self, second_ci, relation_type="contain",
|
||||
page=1, per_page=None):
|
||||
"""only for CI Type
|
||||
"""
|
||||
if per_page is None:
|
||||
per_page = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
first_cis = db.session.query(CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id == second_ci).filter(
|
||||
CIRelation.relation_type == relation_type)
|
||||
numfound = first_cis.count()
|
||||
first_cis = first_cis.offset(
|
||||
(page - 1) * per_page).limit(per_page).all()
|
||||
result = []
|
||||
first_ci_ids = [str(first_ci.first_ci_id) for first_ci in first_cis]
|
||||
total = len(first_ci_ids)
|
||||
if first_ci_ids:
|
||||
result = get_cis_by_ids(first_ci_ids)
|
||||
return numfound, total, result
|
||||
|
||||
def get_grandfather(self, ci_id, relation_type="contain"):
|
||||
"""only for CI Type
|
||||
"""
|
||||
grandfather = db.session.query(CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id.in_(db.session.query(
|
||||
CIRelation.first_ci_id).filter(
|
||||
CIRelation.second_ci_id == ci_id).filter(
|
||||
CIRelation.relation_type == relation_type))).filter(
|
||||
CIRelation.relation_type == relation_type).first()
|
||||
if grandfather:
|
||||
return CIManager().get_ci_by_id(grandfather.first_ci_id,
|
||||
need_children=False)
|
||||
|
||||
def add(self, first_ci, second_ci, more=None, relation_type="contain"):
|
||||
ci = db.session.query(CI.ci_id).filter(CI.ci_id == first_ci).first()
|
||||
if ci is None:
|
||||
return abort(404, "first_ci {0} is not existed".format(first_ci))
|
||||
c = db.session.query(CI.ci_id).filter(CI.ci_id == second_ci).first()
|
||||
if c is None:
|
||||
return abort(404, "second_ci {0} is not existed".format(
|
||||
second_ci))
|
||||
existed = db.session.query(CIRelation.cr_id).filter(
|
||||
CIRelation.first_ci_id == first_ci).filter(
|
||||
CIRelation.second_ci_id == second_ci).first()
|
||||
if existed is not None:
|
||||
return existed.cr_id
|
||||
cr = CIRelation()
|
||||
cr.first_ci_id = first_ci
|
||||
cr.second_ci_id = second_ci
|
||||
if more is not None:
|
||||
cr.more = more
|
||||
cr.relation_type = relation_type
|
||||
db.session.add(cr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add CIRelation is error, {0}".format(
|
||||
str(e)))
|
||||
return abort(400, "add CIRelation is error, {0}".format(str(e)))
|
||||
# write history
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr.cr_id, cr.first_ci_id, cr.second_ci_id,
|
||||
relation_type, operate_type="add")
|
||||
return cr.cr_id
|
||||
|
||||
def delete(self, cr_id):
|
||||
cr = db.session.query(CIRelation).filter(
|
||||
CIRelation.cr_id == cr_id).first()
|
||||
cr_id = cr.cr_id
|
||||
first_ci = cr.first_ci_id
|
||||
second_ci = cr.second_ci_id
|
||||
if cr is not None:
|
||||
db.session.delete(cr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"delete CIRelation is error, {0}".format(str(e)))
|
||||
return abort(
|
||||
400, "delete CIRelation is error, {0}".format(str(e)))
|
||||
his_manager = CIRelationHistoryManager()
|
||||
his_manager.add(cr_id, first_ci, second_ci,
|
||||
cr.relation_type, operate_type="delete")
|
||||
return True
|
||||
return abort(404, "CI relation is not existed")
|
||||
|
||||
def delete_2(self, first_ci, second_ci):
|
||||
cr = db.session.query(CIRelation).filter(
|
||||
CIRelation.first_ci_id == first_ci).filter(
|
||||
CIRelation.second_ci_id == second_ci).first()
|
||||
return self.delete(cr.cr_id)
|
||||
|
||||
|
||||
class HostNumStatis(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_hosts_by_project(self, project_id_list=None):
|
||||
res = {}
|
||||
if not project_id_list:
|
||||
project = CITypeCache.get("project")
|
||||
projects = db.session.query(CI.ci_id).filter(
|
||||
CI.type_id == project.type_id).all()
|
||||
project_id_list = (project.ci_id for project in projects)
|
||||
project_id_list = map(str, project_id_list)
|
||||
project_ids = ",".join(project_id_list)
|
||||
nums = db.session.execute(QUERY_HOSTS_NUM_BY_PROJECT.format(
|
||||
"".join(["(", project_ids, ")"]))).fetchall()
|
||||
if nums:
|
||||
for ci_id in project_id_list:
|
||||
res[int(ci_id)] = 0
|
||||
for ci_id, num in nums:
|
||||
res[ci_id] = num
|
||||
return res
|
||||
|
||||
def get_hosts_by_product(self, product_id_list=None):
|
||||
res = {}
|
||||
if not product_id_list:
|
||||
product = CITypeCache.get("product")
|
||||
products = db.session.query(CI.ci_id).filter(
|
||||
CI.type_id == product.type_id).all()
|
||||
product_id_list = (product.ci_id for product in products)
|
||||
product_id_list = map(str, product_id_list)
|
||||
product_ids = ",".join(product_id_list)
|
||||
nums = db.session.execute(QUERY_HOSTS_NUM_BY_PRODUCT.format(
|
||||
"".join(["(", product_ids, ")"]))).fetchall()
|
||||
if nums:
|
||||
for ci_id in product_id_list:
|
||||
res[int(ci_id)] = 0
|
||||
for ci_id, num in nums:
|
||||
res[ci_id] = num
|
||||
return res
|
||||
|
||||
def get_hosts_by_bu(self, bu_id_list=None):
|
||||
res = {}
|
||||
if not bu_id_list:
|
||||
bu = CITypeCache.get("bu")
|
||||
bus = db.session.query(CI.ci_id).filter(
|
||||
CI.type_id == bu.type_id).all()
|
||||
bu_id_list = (bu.ci_id for bu in bus)
|
||||
bu_id_list = map(str, bu_id_list)
|
||||
bu_ids = ",".join(bu_id_list)
|
||||
current_app.logger.debug(QUERY_HOSTS_NUM_BY_BU.format(
|
||||
"".join(["(", bu_ids, ")"])))
|
||||
if not bu_ids:
|
||||
return res
|
||||
nums = db.session.execute(
|
||||
QUERY_HOSTS_NUM_BY_BU.format(
|
||||
"".join(["(", bu_ids, ")"]))).fetchall()
|
||||
if nums:
|
||||
for ci_id in bu_id_list:
|
||||
res[int(ci_id)] = 0
|
||||
for ci_id, num in nums:
|
||||
res[ci_id] = num
|
||||
return res
|
||||
|
||||
|
||||
def get_cis_by_ids(ci_ids, ret_key="name", fields="", value_tables=None):
|
||||
""" argument ci_ids are string list of CI instance ID, eg. ['1', '2']
|
||||
"""
|
||||
if not ci_ids:
|
||||
return []
|
||||
start = time.time()
|
||||
ci_id_tuple = tuple(map(int, ci_ids))
|
||||
res = rd.get(ci_id_tuple)
|
||||
if res is not None and None not in res and ret_key == "name":
|
||||
res = map(json.loads, res)
|
||||
if not fields:
|
||||
return res
|
||||
else:
|
||||
_res = []
|
||||
for d in res:
|
||||
_d = dict()
|
||||
_d["_id"], _d["_type"] = d.get("_id"), d.get("_type")
|
||||
_d["ci_type"] = d.get("ci_type")
|
||||
for field in fields:
|
||||
_d[field] = d.get(field)
|
||||
_res.append(_d)
|
||||
current_app.logger.debug("filter time: %s" % (time.time() - start))
|
||||
return _res
|
||||
current_app.logger.warning("cache not hit...............")
|
||||
if not fields:
|
||||
_fields = ""
|
||||
else:
|
||||
_fields = list()
|
||||
for field in fields:
|
||||
attr = CIAttributeCache.get(field)
|
||||
if attr is not None:
|
||||
_fields.append(str(attr.attr_id))
|
||||
_fields = "WHERE A.attr_id in ({0})".format(",".join(_fields))
|
||||
ci_ids = ",".join(ci_ids)
|
||||
if value_tables is None:
|
||||
value_tables = type_map["table_name"].values()
|
||||
current_app.logger.debug(value_tables)
|
||||
value_sql = " UNION ".join([QUERY_CIS_BY_VALUE_TABLE.format(value_table,
|
||||
ci_ids)
|
||||
for value_table in value_tables])
|
||||
query_sql = QUERY_CIS_BY_IDS.format(ci_ids, _fields, value_sql)
|
||||
current_app.logger.debug(query_sql)
|
||||
start = time.time()
|
||||
hosts = db.session.execute(query_sql).fetchall()
|
||||
current_app.logger.info("get cis time is: {0}".format(
|
||||
time.time() - start))
|
||||
|
||||
ci_list = set()
|
||||
res = list()
|
||||
ci_dict = dict()
|
||||
start = time.time()
|
||||
for ci_id, type_id, attr_id, attr_name, \
|
||||
attr_alias, value, value_type, is_multivalue in hosts:
|
||||
if ci_id not in ci_list:
|
||||
ci_dict = dict()
|
||||
ci_type = CITypeSpecCache.get(type_id)
|
||||
ci_dict["_id"] = ci_id
|
||||
ci_dict["_type"] = type_id
|
||||
ci_dict["ci_type"] = ci_type.type_name
|
||||
ci_dict["ci_type_alias"] = ci_type.type_alias
|
||||
ci_list.add(ci_id)
|
||||
res.append(ci_dict)
|
||||
if ret_key == "name":
|
||||
if is_multivalue:
|
||||
if isinstance(ci_dict.get(attr_name), list):
|
||||
ci_dict[attr_name].append(value)
|
||||
else:
|
||||
ci_dict[attr_name] = [value]
|
||||
else:
|
||||
ci_dict[attr_name] = value
|
||||
elif ret_key == "alias":
|
||||
if is_multivalue:
|
||||
if isinstance(ci_dict.get(attr_alias), list):
|
||||
ci_dict[attr_alias].append(value)
|
||||
else:
|
||||
ci_dict[attr_alias] = [value]
|
||||
else:
|
||||
ci_dict[attr_alias] = value
|
||||
elif ret_key == "id":
|
||||
if is_multivalue:
|
||||
if isinstance(ci_dict.get(attr_id), list):
|
||||
ci_dict[attr_id].append(value)
|
||||
else:
|
||||
ci_dict[attr_id] = [value]
|
||||
else:
|
||||
ci_dict[attr_id] = value
|
||||
|
||||
current_app.logger.debug("result parser time is: {0}".format(
|
||||
time.time() - start))
|
||||
return res
|
316
lib/ci_type.py
316
lib/ci_type.py
|
@ -1,316 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask import current_app
|
||||
from flask import abort
|
||||
|
||||
from extensions import db
|
||||
from models import row2dict
|
||||
from models.ci_type import CITypeAttribute
|
||||
from models.ci_type import CIType
|
||||
from models.ci_type import CITypeAttributeCache
|
||||
from models.ci_type import CITypeCache
|
||||
from models.ci_type_relation import CITypeRelation
|
||||
from models.attribute import CIAttributeCache
|
||||
from lib.attribute import AttributeManager
|
||||
|
||||
|
||||
class CITypeAttributeManager(object):
|
||||
"""
|
||||
manage CIType's attributes, include query, add, update, delete
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_attributes_by_type_id(self, type_id):
|
||||
attrs = CITypeAttributeCache.get(type_id)
|
||||
attr_manager = AttributeManager()
|
||||
result = list()
|
||||
for attr in attrs:
|
||||
attr_dict = attr_manager.get_attribute_by_id(attr.attr_id)
|
||||
attr_dict["is_required"] = attr.is_required
|
||||
attr_dict["order"] = attr.order
|
||||
result.append(attr_dict)
|
||||
return result
|
||||
|
||||
def add(self, type_id, attr_ids=None, is_required=False):
|
||||
"""
|
||||
add attributes to CIType, attr_ids are list
|
||||
"""
|
||||
if not attr_ids or not isinstance(attr_ids, list):
|
||||
return abort(500, "attr_ids must be required")
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return abort(404, "CIType ID({0}) is not existed".format(type_id))
|
||||
for attr_id in attr_ids:
|
||||
attr = CIAttributeCache.get(attr_id)
|
||||
if attr is None:
|
||||
return abort(404,
|
||||
"attribute id {0} is not existed".format(attr_id))
|
||||
existed = db.session.query(CITypeAttribute.attr_id).filter_by(
|
||||
type_id=type_id).filter_by(attr_id=attr_id).first()
|
||||
if existed is not None:
|
||||
continue
|
||||
current_app.logger.debug(attr_id)
|
||||
db.session.add(CITypeAttribute(
|
||||
type_id=type_id, attr_id=attr_id, is_required=is_required))
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"add attribute to CIType is error, {0}".format(str(e)))
|
||||
return abort(
|
||||
500, "add attribute to CIType is error, maybe duplicate entry")
|
||||
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
return True
|
||||
|
||||
def delete(self, type_id, attr_ids=None):
|
||||
"""
|
||||
delete attributes at CIType, attr_ids are list
|
||||
"""
|
||||
if not attr_ids or not isinstance(attr_ids, list):
|
||||
return abort(
|
||||
500, "delete attribute of CIType, attr_ids must be required")
|
||||
ci_type = CITypeCache.get(type_id)
|
||||
if ci_type is None:
|
||||
return abort(
|
||||
404, "CIType ID({0}) is not existed".format(type_id))
|
||||
for attr_id in attr_ids:
|
||||
attr = CIAttributeCache.get(attr_id)
|
||||
if attr is None:
|
||||
return abort(
|
||||
404, "attribute id {0} is not existed".format(attr_id))
|
||||
db.session.query(CITypeAttribute).filter_by(
|
||||
type_id=type_id).filter_by(attr_id=attr_id).delete()
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"delete attributes of CIType is error, {0}".format(str(e)))
|
||||
return abort(500, "delete attributes of CIType is error")
|
||||
CITypeAttributeCache.clean(type_id)
|
||||
return True
|
||||
|
||||
|
||||
class CITypeManager(object):
|
||||
"""
|
||||
manage CIType
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_citypes(self, type_name=None):
|
||||
ci_types = db.session.query(CIType).all() if type_name is None else \
|
||||
db.session.query(CIType).filter(
|
||||
CIType.type_name.ilike("%{0}%".format(type_name))).all()
|
||||
res = list()
|
||||
for ci_type in ci_types:
|
||||
type_dict = row2dict(ci_type)
|
||||
type_dict["uniq_key"] = CIAttributeCache.get(
|
||||
type_dict["uniq_id"]).attr_name
|
||||
res.append(type_dict)
|
||||
return res
|
||||
|
||||
def query(self, _type):
|
||||
citype = CITypeCache.get(_type)
|
||||
if citype:
|
||||
return row2dict(citype)
|
||||
return abort(404, "citype is not found")
|
||||
|
||||
def add(self, type_name, type_alias, _id=None, unique=None,
|
||||
icon_url="", enabled=True):
|
||||
uniq_key = CIAttributeCache.get(_id) or CIAttributeCache.get(unique)
|
||||
if uniq_key is None:
|
||||
return False, "uniq_key is not existed"
|
||||
citype = CITypeCache.get(type_name)
|
||||
if citype:
|
||||
return False, "this CIType {0} is existed".format(type_name)
|
||||
_citype = CIType()
|
||||
_citype.type_name = type_name
|
||||
_citype.type_alias = type_alias
|
||||
_citype.uniq_id = uniq_key.attr_id
|
||||
_citype.enabled = enabled
|
||||
_citype.icon_url = icon_url
|
||||
db.session.add(_citype)
|
||||
db.session.flush()
|
||||
_citype_attr = CITypeAttribute()
|
||||
_citype_attr.attr_id = uniq_key.attr_id
|
||||
_citype_attr.type_id = _citype.type_id
|
||||
_citype_attr.is_required = True
|
||||
db.session.add(_citype_attr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add CIType is error, {0}".format(str(e)))
|
||||
return False, str(e)
|
||||
CITypeCache.clean(type_name)
|
||||
return True, _citype.type_id
|
||||
|
||||
def update(self, type_id, type_name, type_alias, _id=None, unique=None,
|
||||
icon_url="", enabled=None):
|
||||
citype = CITypeCache.get(type_id)
|
||||
if citype is None:
|
||||
return False, "CIType {0} is not existed".format(type_name)
|
||||
uniq_key = CIAttributeCache.get(_id) or CIAttributeCache.get(unique)
|
||||
if uniq_key is not None:
|
||||
citype.uniq_id = uniq_key.attr_id
|
||||
citype_attr = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == type_id).filter(
|
||||
CITypeAttribute.attr_id == uniq_key.attr_id).first()
|
||||
if citype_attr is None:
|
||||
citype_attr = CITypeAttribute()
|
||||
citype_attr.attr_id = uniq_key.attr_id
|
||||
citype_attr.type_id = type_id
|
||||
citype_attr.is_required = True
|
||||
db.session.add(citype_attr)
|
||||
if type_name:
|
||||
citype.type_name = type_name
|
||||
if type_alias:
|
||||
citype.type_alias = type_alias
|
||||
if icon_url:
|
||||
citype.icon_url = icon_url
|
||||
if enabled is not None:
|
||||
citype.enabled = enabled
|
||||
db.session.add(citype)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("add CIType is error, {0}".format(str(e)))
|
||||
return False, str(e)
|
||||
CITypeCache.clean(type_id)
|
||||
return True, type_id
|
||||
|
||||
def set_enabled(self, type_id, enabled=True):
|
||||
citype = CITypeCache.get(type_id)
|
||||
if citype is None:
|
||||
return abort(404, "CIType[{0}] is not existed".format(type_id))
|
||||
citype.enabled = enabled
|
||||
db.session.add(citype)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"set CIType enabled is error, {0}".format(str(e)))
|
||||
return abort(500, str(e))
|
||||
return type_id
|
||||
|
||||
def delete(self, type_id):
|
||||
citype = db.session.query(CIType).filter_by(type_id=type_id).first()
|
||||
type_name = citype.type_name
|
||||
if citype:
|
||||
db.session.delete(citype)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"delete CIType is error, {0}".format(str(e)))
|
||||
return abort(500, str(e))
|
||||
CITypeCache.clean(type_id)
|
||||
return "CIType {0} deleted".format(type_name)
|
||||
return abort(404, "CIType is not existed")
|
||||
|
||||
|
||||
class CITypeRelationManager(object):
|
||||
"""
|
||||
manage relation between CITypes
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def relation_types(self):
|
||||
""" all CIType relation types
|
||||
"""
|
||||
from lib.const import CITYPE_RELATION_TYPES
|
||||
|
||||
return CITYPE_RELATION_TYPES
|
||||
|
||||
def get_children(self, parent_id):
|
||||
children = db.session.query(CITypeRelation).filter(
|
||||
CITypeRelation.parent_id == parent_id).all()
|
||||
result = []
|
||||
for child in children:
|
||||
ctr_id = child.ctr_id
|
||||
citype = CITypeCache.get(child.child_id)
|
||||
citype_dict = row2dict(citype)
|
||||
citype_dict["ctr_id"] = ctr_id
|
||||
manager = CITypeAttributeManager()
|
||||
citype_dict["attributes"] = manager.get_attributes_by_type_id(
|
||||
citype.type_id)
|
||||
citype_dict["relation_type"] = child.relation_type
|
||||
result.append(citype_dict)
|
||||
return result
|
||||
|
||||
def get_parents(self, child_id):
|
||||
parents = db.session.query(CITypeRelation).filter(
|
||||
CITypeRelation.child_id == child_id).all()
|
||||
result = []
|
||||
for parent in parents:
|
||||
ctr_id = parent.ctr_id
|
||||
citype = CITypeCache.get(parent.parent_id)
|
||||
citype_dict = row2dict(citype)
|
||||
citype_dict["ctr_id"] = ctr_id
|
||||
manager = CITypeAttributeManager()
|
||||
citype_dict["attributes"] = manager.get_attributes_by_type_id(
|
||||
citype.type_id)
|
||||
citype_dict["relation_type"] = parent.relation_type
|
||||
result.append(citype_dict)
|
||||
return result
|
||||
|
||||
def add(self, parent, child, relation_type="contain"):
|
||||
p = CITypeCache.get(parent)
|
||||
if p is None:
|
||||
return abort(404, "parent {0} is not existed".format(parent))
|
||||
c = CITypeCache.get(child)
|
||||
if c is None:
|
||||
return abort(404, "child {0} is not existed".format(child))
|
||||
existed = db.session.query(CITypeRelation.ctr_id).filter_by(
|
||||
parent_id=parent).filter_by(child_id=child).first()
|
||||
if existed is not None:
|
||||
return True, existed.ctr_id
|
||||
ctr = CITypeRelation()
|
||||
ctr.parent_id = parent
|
||||
ctr.child_id = child
|
||||
ctr.relation_type = relation_type
|
||||
db.session.add(ctr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"add CITypeRelation is error, {0}".format(str(e)))
|
||||
return abort(
|
||||
500, "add CITypeRelation is error, {0}".format(str(e)))
|
||||
return ctr.ctr_id
|
||||
|
||||
def delete(self, ctr_id):
|
||||
ctr = db.session.query(CITypeRelation).filter(
|
||||
CITypeRelation.ctr_id == ctr_id).first()
|
||||
if ctr:
|
||||
db.session.delete(ctr)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"delete CITypeRelation is error, {0}".format(str(e)))
|
||||
return abort(
|
||||
500, "delete CITypeRelation is error, {0}".format(str(e)))
|
||||
return True
|
||||
return abort(404, "CIType relation is not existed")
|
||||
|
||||
def delete_2(self, parent, child):
|
||||
ctr = db.session.query(CITypeRelation).filter(
|
||||
CITypeRelation.parent_id == parent).filter(
|
||||
CITypeRelation.child_id == child).first()
|
||||
return self.delete(ctr.ctr_id)
|
101
lib/const.py
101
lib/const.py
|
@ -1,101 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from markupsafe import escape
|
||||
|
||||
from models.attribute import TextChoice
|
||||
from models.attribute import FloatChoice
|
||||
from models.attribute import IntegerChoice
|
||||
from models.attribute import CIAttributeCache
|
||||
from models.ci_value import CIValueText
|
||||
from models.ci_value import CIValueInteger
|
||||
from models.ci_value import CIValueFloat
|
||||
from models.ci_value import CIValueDateTime
|
||||
from models.ci_value import CIIndexValueDateTime
|
||||
from models.ci_value import CIIndexValueFloat
|
||||
from models.ci_value import CIIndexValueInteger
|
||||
from models.ci_value import CIIndexValueText
|
||||
|
||||
|
||||
def string2int(x):
|
||||
return int(float(x))
|
||||
|
||||
|
||||
def str2datetime(x):
|
||||
try:
|
||||
v = datetime.datetime.strptime(x, "%Y-%m-%d")
|
||||
return v
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
v = datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
|
||||
return v
|
||||
except ValueError:
|
||||
pass
|
||||
raise ValueError
|
||||
|
||||
|
||||
type_map = {
|
||||
'converter': {
|
||||
'int': string2int,
|
||||
'float': float,
|
||||
'text': escape,
|
||||
'datetime': str2datetime,
|
||||
},
|
||||
'choice': {
|
||||
'int': IntegerChoice,
|
||||
'float': FloatChoice,
|
||||
'text': TextChoice,
|
||||
},
|
||||
'table': {
|
||||
'int': CIValueInteger,
|
||||
'text': CIValueText,
|
||||
'datetime': CIValueDateTime,
|
||||
'float': CIValueFloat,
|
||||
'index_int': CIIndexValueInteger,
|
||||
'index_text': CIIndexValueText,
|
||||
'index_datetime': CIIndexValueDateTime,
|
||||
'index_float': CIIndexValueFloat,
|
||||
},
|
||||
'table_name': {
|
||||
'int': 'integers',
|
||||
'text': 'texts',
|
||||
'datetime': 'datetime',
|
||||
'float': 'floats',
|
||||
'index_int': 'index_integers',
|
||||
'index_text': 'index_texts',
|
||||
'index_datetime': 'index_datetime',
|
||||
'index_float': 'index_floats',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TableMap():
|
||||
def __init__(self, attr_name=None):
|
||||
self.attr_name = attr_name
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
if self.attr_name is not None:
|
||||
attr = CIAttributeCache.get(self.attr_name)
|
||||
if attr.is_index:
|
||||
i = "index_{0}".format(attr.value_type)
|
||||
else:
|
||||
i = attr.value_type
|
||||
return type_map["table"].get(i)
|
||||
|
||||
@property
|
||||
def table_name(self):
|
||||
if self.attr_name is not None:
|
||||
attr = CIAttributeCache.get(self.attr_name)
|
||||
if attr.is_index:
|
||||
i = "index_{0}".format(attr.value_type)
|
||||
else:
|
||||
i = attr.value_type
|
||||
return type_map["table_name"].get(i)
|
||||
|
||||
|
||||
CITYPE_RELATION_TYPES = ["connect", "deploy", "install", "contain"]
|
||||
CI_RELATION_TYPES = ["connect", "deploy", "install", "contain"]
|
|
@ -1,74 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
from flask import request
|
||||
from flask import render_template
|
||||
from flask import current_app
|
||||
|
||||
from lib.exception import InvalidUsageError
|
||||
|
||||
|
||||
def templated(template=None):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
template_name = template
|
||||
if template_name is None:
|
||||
template_name = request.endpoint.replace('.', '/') + '.html'
|
||||
ctx = f(*args, **kwargs)
|
||||
if ctx is None:
|
||||
ctx = {}
|
||||
elif not isinstance(ctx, dict):
|
||||
return ctx
|
||||
return render_template(template_name, **ctx)
|
||||
|
||||
return decorated_function
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def argument_required1(*args_required):
|
||||
from manage import InvalidUsageError
|
||||
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
for arg in args_required:
|
||||
if request.values.get(arg, None) is None:
|
||||
raise InvalidUsageError(
|
||||
"argument {0} is required".format(arg), 400)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class argument_required(object):
|
||||
def __init__(self, *args):
|
||||
self.args = args
|
||||
|
||||
def __enter__(self):
|
||||
for arg in self.args:
|
||||
if not request.values.get(arg):
|
||||
raise InvalidUsageError(
|
||||
"argument {0} is required".format(arg), status_code=400)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
|
||||
def url_statistic(f):
|
||||
@wraps(f)
|
||||
def decorated_func(*args, **kwargs):
|
||||
start = time.time()
|
||||
r = f(*args, **kwargs)
|
||||
spend = time.time() - start
|
||||
url = request.path
|
||||
current_app.logger.info(url)
|
||||
current_app.logger.info(spend)
|
||||
return r
|
||||
return decorated_func
|
|
@ -1,17 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
class InvalidUsageError(Exception):
|
||||
status_code = 400
|
||||
|
||||
def __init__(self, message, status_code=None, payload=None):
|
||||
Exception.__init__(self)
|
||||
self.message = message
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
self.payload = payload
|
||||
|
||||
def to_dict(self):
|
||||
rv = dict(self.payload or ())
|
||||
rv['message'] = self.message
|
||||
return rv
|
|
@ -1,75 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
|
||||
from extensions import db
|
||||
from models.history import OperationRecord
|
||||
from models.history import CIAttributeHistory
|
||||
from models.history import CIRelationHistory
|
||||
|
||||
|
||||
class CIAttributeHistoryManger(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def add(self, ci_id, history_list):
|
||||
if history_list:
|
||||
record = OperationRecord()
|
||||
record.uid = g.user.uid
|
||||
record.timestamp = datetime.datetime.now()
|
||||
db.session.add(record)
|
||||
db.session.commit()
|
||||
for attr_id, operate_type, old, new in history_list:
|
||||
history = CIAttributeHistory()
|
||||
history.attr_id = attr_id
|
||||
history.operate_type = operate_type
|
||||
history.old = old
|
||||
history.new = new
|
||||
history.ci_id = ci_id
|
||||
history.record_id = record.record_id
|
||||
db.session.add(history)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"add attribute history error, {0}".format(str(e)))
|
||||
return False, "add attribute history error, {0}".format(str(e))
|
||||
return True, None
|
||||
|
||||
|
||||
class CIRelationHistoryManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def add(self, relation, first_ci, second_ci,
|
||||
relation_type, operate_type="add"):
|
||||
record = OperationRecord()
|
||||
record.uid = g.user.uid
|
||||
record.timestamp = datetime.datetime.now()
|
||||
db.session.add(record)
|
||||
db.session.flush()
|
||||
|
||||
history = CIRelationHistory()
|
||||
history.relation = relation
|
||||
history.record_id = record.record_id
|
||||
history.operate_type = operate_type
|
||||
history.first_ci_id = first_ci
|
||||
history.second_ci_id = second_ci
|
||||
history.relation_type = relation_type
|
||||
db.session.add(history)
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"add relation history is error, {0}".format(str(e)))
|
||||
return False, "add relation history is error, {0}".format(str(e))
|
||||
return True, None
|
107
lib/query_sql.py
107
lib/query_sql.py
|
@ -1,107 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
QUERY_HOSTS_BY_APP = """
|
||||
SELECT *
|
||||
FROM cis
|
||||
INNER JOIN ci_relations AS cr ON cis.`ci_id`=cr.`second_ci`
|
||||
WHERE cr.`first_ci` = {0:d} LIMIT {1:d}, {2:d};
|
||||
"""
|
||||
|
||||
QUERY_HOSTS_NUM_BY_PROJECT = """
|
||||
SELECT cr.first_ci_id,
|
||||
count(DISTINCT cr.second_ci_id)
|
||||
FROM ci_relations AS cr
|
||||
WHERE cr.first_ci_id IN {0}
|
||||
GROUP BY cr.first_ci_id
|
||||
"""
|
||||
|
||||
QUERY_HOSTS_NUM_BY_BU = """
|
||||
SELECT B.first_ci_id,
|
||||
count(DISTINCT cr.second_ci_id)
|
||||
FROM
|
||||
(SELECT A.first_ci_id,
|
||||
cr.second_ci_id
|
||||
FROM
|
||||
(SELECT cr.first_ci_id,
|
||||
cis.ci_id
|
||||
FROM cis
|
||||
INNER JOIN ci_relations AS cr ON cis.ci_id=cr.second_ci_id
|
||||
WHERE cr.first_ci_id IN {0}) AS A
|
||||
INNER JOIN ci_relations AS cr ON cr.first_ci_id=A.ci_id) AS B
|
||||
INNER JOIN ci_relations AS cr ON B.second_ci_id=cr.first_ci_id
|
||||
GROUP BY B.first_ci_id
|
||||
"""
|
||||
|
||||
QUERY_HOSTS_NUM_BY_PRODUCT = """
|
||||
SELECT A.first_ci_id,
|
||||
count(DISTINCT cr.second_ci_id)
|
||||
FROM
|
||||
(SELECT cr.first_ci_id,
|
||||
cis.ci_id
|
||||
FROM cis
|
||||
INNER JOIN ci_relations AS cr ON cis.ci_id=cr.second_ci_id
|
||||
WHERE cr.first_ci_id IN {0}) AS A
|
||||
INNER JOIN ci_relations AS cr ON cr.first_ci_id=A.ci_id
|
||||
GROUP BY A.first_ci_id;
|
||||
"""
|
||||
|
||||
QUERY_CIS_BY_VALUE_TABLE = """
|
||||
SELECT attr.attr_name,
|
||||
attr.attr_alias,
|
||||
attr.value_type,
|
||||
attr.is_multivalue,
|
||||
cis.type_id,
|
||||
{0}.ci_id,
|
||||
{0}.attr_id,
|
||||
{0}.value
|
||||
FROM {0}
|
||||
INNER JOIN cis ON {0}.ci_id=cis.ci_id
|
||||
AND {0}.`ci_id` IN ({1})
|
||||
INNER JOIN ci_attributes as attr ON attr.attr_id = {0}.attr_id
|
||||
"""
|
||||
|
||||
QUERY_CIS_BY_IDS = """
|
||||
SELECT A.ci_id,
|
||||
A.type_id,
|
||||
A.attr_id,
|
||||
A.attr_name,
|
||||
A.attr_alias,
|
||||
A.value,
|
||||
A.value_type,
|
||||
A.is_multivalue
|
||||
FROM
|
||||
({2}) AS A {1}
|
||||
ORDER BY A.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY1 = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN ci_attributes AS attr ON attr.attr_id={0}.attr_id
|
||||
WHERE attr.attr_name="{1}"
|
||||
GROUP BY {0}.ci_id;
|
||||
"""
|
||||
|
||||
FACET_QUERY = """
|
||||
SELECT {0}.value,
|
||||
count({0}.ci_id)
|
||||
FROM {0}
|
||||
INNER JOIN ({1}) AS F ON F.ci_id={0}.ci_id
|
||||
WHERE {0}.attr_id={2:d}
|
||||
GROUP BY {0}.value
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_ATTR_NAME = """
|
||||
SELECT {0}.ci_id
|
||||
FROM {0}
|
||||
WHERE {0}.attr_id={1:d}
|
||||
AND {0}.value {2}
|
||||
"""
|
||||
|
||||
QUERY_CI_BY_TYPE = """
|
||||
SELECT cis.ci_id
|
||||
FROM cis
|
||||
WHERE cis.type_id in ({0})
|
||||
"""
|
365
lib/search.py
365
lib/search.py
|
@ -1,365 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from lib.const import TableMap
|
||||
from models.attribute import CIAttributeCache
|
||||
from models.ci_type import CITypeCache
|
||||
from extensions import db
|
||||
from models.ci import CI
|
||||
from lib.ci import get_cis_by_ids
|
||||
from lib.query_sql import FACET_QUERY
|
||||
from lib.query_sql import QUERY_CI_BY_TYPE
|
||||
from lib.query_sql import QUERY_CI_BY_ATTR_NAME
|
||||
|
||||
|
||||
class SearchError(Exception):
|
||||
def __init__(self, v):
|
||||
self.v = v
|
||||
|
||||
def __str__(self):
|
||||
return self.v
|
||||
|
||||
|
||||
class Search(object):
|
||||
def __init__(self, query=None, fl=None, facet_field=None,
|
||||
page=1, ret_key="name", count=1, sort=None):
|
||||
self.orig_query = query
|
||||
self.fl = fl
|
||||
self.facet_field = facet_field
|
||||
self.page = page
|
||||
self.ret_key = ret_key
|
||||
try:
|
||||
self.count = int(count)
|
||||
except ValueError:
|
||||
self.count = current_app.config.get("DEFAULT_PAGE_COUNT")
|
||||
self.sort = sort
|
||||
self.query_sql = ""
|
||||
self.type_id_list = []
|
||||
|
||||
def tor_proc(self, key):
|
||||
tor = list()
|
||||
if key.startswith("+"):
|
||||
tor.append('&')
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("-"):
|
||||
tor.append('|')
|
||||
key = key[1:].strip()
|
||||
elif key.startswith("~"):
|
||||
tor.append('~')
|
||||
key = key[1:].strip()
|
||||
if not tor:
|
||||
tor = ['&', '']
|
||||
if len(tor) < 2:
|
||||
tor.append('')
|
||||
return tor, key
|
||||
|
||||
def attr_name_proc(self, key):
|
||||
tor, key = self.tor_proc(key)
|
||||
if key in ('ci_type', 'type', '_type'):
|
||||
return '_type', 'text', tor, None
|
||||
if key in ('id', 'ci_id', '_id'):
|
||||
return '_id', 'text', tor, None
|
||||
attr = CIAttributeCache.get(key)
|
||||
if attr is not None:
|
||||
# if not attr.is_index:
|
||||
# raise SearchError("{0} is not indexed".format(attr.attr_name))
|
||||
field_name = attr.attr_name
|
||||
return field_name, attr.value_type, tor, attr
|
||||
else:
|
||||
raise SearchError("{0} is not existed".format(key))
|
||||
|
||||
def type_query_handler(self, v, only_type_query):
|
||||
new_v = [v]
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
new_v = v[1:-1].split(";")
|
||||
for _v in new_v:
|
||||
ci_type = CITypeCache.get(_v)
|
||||
if ci_type is not None:
|
||||
self.type_id_list.append(str(ci_type.type_id))
|
||||
if self.type_id_list:
|
||||
type_ids = ",".join(self.type_id_list)
|
||||
_query_sql = QUERY_CI_BY_TYPE.format(type_ids)
|
||||
if only_type_query:
|
||||
return _query_sql
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
def in_query_handler(self, attr, v):
|
||||
new_v = v[1:-1].split(";")
|
||||
table_name = TableMap(attr_name=attr.attr_name).table_name
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.attr_id,
|
||||
" OR {0}.value ".format(table_name).join(['LIKE "{0}"'.format(
|
||||
_v.replace("*", "%")) for _v in new_v]))
|
||||
return _query_sql
|
||||
|
||||
def range_query_handler(self, attr, v):
|
||||
start, end = [x.strip() for x in v[1:-1].split("_TO_")]
|
||||
table_name = TableMap(attr_name=attr.attr_name).table_name
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.attr_id, "BETWEEN '{0}' AND '{1}'".format(
|
||||
start.replace("*", "%"), end.replace("*", "%")))
|
||||
return _query_sql
|
||||
|
||||
def comparison_query_handler(self, attr, v):
|
||||
table_name = TableMap(attr_name=attr.attr_name).table_name
|
||||
if (v.startswith("<") and not v.startswith("<=")) or \
|
||||
(v.startswith(">") and not v.startswith(">=")):
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.attr_id, "{0} '{1}'".format(
|
||||
v[0], v[1:].replace("*", "%")))
|
||||
elif v.startswith(">=") or v.startswith("<="):
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.attr_id, "{0} '{1}'".format(
|
||||
v[:2], v[2:].replace("*", "%")))
|
||||
return _query_sql
|
||||
|
||||
def sort_query_handler(self, field, query_sql, only_type_query):
|
||||
if field is None:
|
||||
field = ""
|
||||
if field.startswith("+"):
|
||||
field = field[1:]
|
||||
sort_type = "ASC"
|
||||
elif field.startswith("-"):
|
||||
field = field[1:]
|
||||
sort_type = "DESC"
|
||||
else:
|
||||
sort_type = "ASC"
|
||||
|
||||
if field in ("_id", "ci_id") or not field:
|
||||
if only_type_query:
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id
|
||||
FROM ({0}) AS B {1}""".format(
|
||||
query_sql,
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
elif self.type_id_list:
|
||||
self.query_sql = """SELECT B.ci_id
|
||||
FROM ({0}) AS B {1}""".format(
|
||||
query_sql,
|
||||
"INNER JOIN cis on cis.ci_id=B.ci_id "
|
||||
"WHERE cis.type_id in ({0}) ".format(
|
||||
",".join(self.type_id_list)))
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id
|
||||
FROM ({0}) AS B {1}""".format(
|
||||
query_sql,
|
||||
"INNER JOIN cis on cis.ci_id=B.ci_id "
|
||||
"WHERE cis.type_id in ({3}) "
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count,
|
||||
",".join(self.type_id_list)))
|
||||
else:
|
||||
self.query_sql = """SELECT B.ci_id
|
||||
FROM ({0}) AS B {1}""".format(
|
||||
query_sql,
|
||||
"INNER JOIN cis on cis.ci_id=B.ci_id ")
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT B.ci_id
|
||||
FROM ({0}) AS B {1}""".format(
|
||||
query_sql,
|
||||
"INNER JOIN cis on cis.ci_id=B.ci_id "
|
||||
"ORDER BY B.ci_id {1} LIMIT {0:d}, {2};".format(
|
||||
(self.page - 1) * self.count, sort_type, self.count))
|
||||
else:
|
||||
attr = CIAttributeCache.get(field)
|
||||
attr_id = attr.attr_id
|
||||
|
||||
table_name = TableMap(attr_name=attr.attr_name).table_name
|
||||
_v_query_sql = """SELECT {0}.ci_id, {1}.value FROM
|
||||
({2}) AS {0} INNER JOIN {1} ON {1}.ci_id = {0}.ci_id
|
||||
WHERE {1}.attr_id = {3}""".format("ALIAS", table_name,
|
||||
query_sql, attr_id)
|
||||
new_table = _v_query_sql
|
||||
if only_type_query:
|
||||
return "SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id " \
|
||||
"FROM ({0}) AS C " \
|
||||
"ORDER BY C.value {2} " \
|
||||
"LIMIT {1:d}, {3};".format(new_table,
|
||||
(self.page - 1) * self.count,
|
||||
sort_type, self.count)
|
||||
elif self.type_id_list:
|
||||
self.query_sql = """SELECT C.ci_id
|
||||
FROM ({0}) AS C
|
||||
INNER JOIN cis on cis.ci_id=C.ci_id
|
||||
WHERE cis.type_id in ({1})""".format(
|
||||
new_table,
|
||||
",".join(self.type_id_list))
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id
|
||||
FROM ({0}) AS C
|
||||
INNER JOIN cis on cis.ci_id=C.ci_id
|
||||
WHERE cis.type_id in ({4})
|
||||
ORDER BY C.value {2}
|
||||
LIMIT {1:d}, {3};""".format(new_table,
|
||||
(self.page - 1) * self.count,
|
||||
sort_type, self.count,
|
||||
",".join(self.type_id_list))
|
||||
else:
|
||||
return """SELECT SQL_CALC_FOUND_ROWS DISTINCT C.ci_id
|
||||
FROM ({0}) AS C
|
||||
ORDER BY C.value {2}
|
||||
LIMIT {1:d}, {3};""".format(new_table,
|
||||
(self.page - 1) * self.count,
|
||||
sort_type, self.count)
|
||||
|
||||
def _wrap_sql(self, tor, alias, _query_sql, query_sql):
|
||||
if tor[0] == "&":
|
||||
query_sql = """SELECT * FROM ({0}) as {1}
|
||||
INNER JOIN ({2}) as {3} USING(ci_id)""".format(
|
||||
query_sql, alias, _query_sql, alias + "A")
|
||||
elif tor[0] == "|":
|
||||
query_sql = "SELECT * FROM ({0}) as {1} UNION ALL ({2})".format(
|
||||
query_sql, alias, _query_sql)
|
||||
elif tor[0] == "~":
|
||||
query_sql = "SELECT * FROM ({0}) as {1} LEFT JOIN ({2}) as {3} " \
|
||||
"USING(ci_id) WHERE {3}.ci_id is NULL".format(
|
||||
query_sql, alias, _query_sql, alias + "A")
|
||||
return query_sql
|
||||
|
||||
def _execute_sql(self, query_sql, only_type_query):
|
||||
v_query_sql = self.sort_query_handler(self.sort, query_sql,
|
||||
only_type_query)
|
||||
start = time.time()
|
||||
execute = db.session.execute
|
||||
current_app.logger.debug(v_query_sql)
|
||||
res = execute(v_query_sql).fetchall()
|
||||
end_time = time.time()
|
||||
current_app.logger.debug("query ci ids time is: {0}".format(
|
||||
end_time - start))
|
||||
numfound = execute("SELECT FOUND_ROWS();").fetchall()[0][0]
|
||||
current_app.logger.debug("statistics ci ids time is: {0}".format(
|
||||
time.time() - end_time)
|
||||
)
|
||||
return numfound, res
|
||||
|
||||
def query_build_raw(self):
|
||||
query_sql, alias, tor = "", "A", ["&"]
|
||||
is_first = True
|
||||
only_type_query = False
|
||||
queries = self.orig_query.split(",")
|
||||
queries = filter(lambda x: x != "", queries)
|
||||
for q in queries:
|
||||
if q.startswith("_type"):
|
||||
queries.remove(q)
|
||||
queries.insert(0, q)
|
||||
if len(queries) == 1 or queries[1].startswith("-") or \
|
||||
queries[1].startswith("~"):
|
||||
only_type_query = True
|
||||
break
|
||||
current_app.logger.debug(queries)
|
||||
special = True
|
||||
for q in queries:
|
||||
_query_sql = ""
|
||||
if ":" in q:
|
||||
k = q.split(":")[0].strip()
|
||||
v = ":".join(q.split(":")[1:]).strip()
|
||||
current_app.logger.info(v)
|
||||
field, field_type, tor, attr = self.attr_name_proc(k)
|
||||
if field == "_type":
|
||||
_query_sql = self.type_query_handler(v, only_type_query)
|
||||
current_app.logger.debug(_query_sql)
|
||||
elif field == "_id": # exclude all others
|
||||
_ci_ids = [str(v)]
|
||||
ci = db.session.query(CI.ci_id).filter(
|
||||
CI.ci_id == int(v)).first()
|
||||
if ci is not None:
|
||||
return 1, _ci_ids
|
||||
elif field:
|
||||
if attr is None:
|
||||
raise SearchError("{0} is not found".format(field))
|
||||
# in query
|
||||
if v.startswith("(") and v.endswith(")"):
|
||||
_query_sql = self.in_query_handler(attr, v)
|
||||
# range query
|
||||
elif v.startswith("[") and v.endswith("]") and "_TO_" in v:
|
||||
_query_sql = self.range_query_handler(attr, v)
|
||||
# comparison query
|
||||
elif v.startswith(">=") or v.startswith("<=") or \
|
||||
v.startswith(">") or v.startswith("<"):
|
||||
_query_sql = self.comparison_query_handler(attr, v)
|
||||
else:
|
||||
table_name = \
|
||||
TableMap(attr_name=attr.attr_name).table_name
|
||||
_query_sql = QUERY_CI_BY_ATTR_NAME.format(
|
||||
table_name, attr.attr_id,
|
||||
'LIKE "{0}"'.format(v.replace("*", "%")))
|
||||
else:
|
||||
return 0, []
|
||||
elif q:
|
||||
return 0, []
|
||||
|
||||
if is_first and _query_sql and not only_type_query:
|
||||
query_sql = "SELECT * FROM ({0}) AS {1}".format(_query_sql,
|
||||
alias)
|
||||
is_first = False
|
||||
alias += "A"
|
||||
elif only_type_query and special:
|
||||
is_first = False
|
||||
special = False
|
||||
query_sql = _query_sql
|
||||
elif _query_sql:
|
||||
query_sql = self._wrap_sql(tor, alias, _query_sql, query_sql)
|
||||
alias += "AA"
|
||||
|
||||
_start = time.time()
|
||||
if query_sql:
|
||||
self.query_sql = query_sql
|
||||
current_app.logger.debug(query_sql)
|
||||
numfound, res = self._execute_sql(query_sql, only_type_query)
|
||||
current_app.logger.info("query ci ids is: {0}".format(
|
||||
time.time() - _start))
|
||||
return numfound, [_res[0] for _res in res]
|
||||
return 0, []
|
||||
|
||||
def facet_build(self):
|
||||
facet = {}
|
||||
for f in self.facet_field:
|
||||
k, field_type, _, attr = self.attr_name_proc(f)
|
||||
if k:
|
||||
table_name = TableMap(attr_name=k).table_name
|
||||
query_sql = FACET_QUERY.format(
|
||||
table_name, self.query_sql, attr.attr_id)
|
||||
current_app.logger.debug(query_sql)
|
||||
result = db.session.execute(query_sql).fetchall()
|
||||
facet[k] = result
|
||||
facet_result = dict()
|
||||
for k, v in facet.items():
|
||||
if not k.startswith('_'):
|
||||
a = getattr(CIAttributeCache.get(k), "attr_%s" % self.ret_key)
|
||||
facet_result[a] = list()
|
||||
for f in v:
|
||||
if f[1] != 0:
|
||||
facet_result[a].append((f[0], f[1], a))
|
||||
return facet_result
|
||||
|
||||
def fl_build(self):
|
||||
_fl = list()
|
||||
for f in self.fl:
|
||||
k, _, _, _ = self.attr_name_proc(f)
|
||||
if k:
|
||||
_fl.append(k)
|
||||
return _fl
|
||||
|
||||
def search(self):
|
||||
numfound, ci_ids = self.query_build_raw()
|
||||
ci_ids = map(str, ci_ids)
|
||||
_fl = self.fl_build()
|
||||
|
||||
if self.facet_field and numfound:
|
||||
facet = self.facet_build()
|
||||
else:
|
||||
facet = dict()
|
||||
|
||||
response, counter = [], {}
|
||||
if ci_ids:
|
||||
response = get_cis_by_ids(ci_ids, ret_key=self.ret_key, fields=_fl)
|
||||
for res in response:
|
||||
ci_type = res.get("ci_type")
|
||||
if ci_type not in counter.keys():
|
||||
counter[ci_type] = 0
|
||||
counter[ci_type] += 1
|
||||
total = len(response)
|
||||
return response, counter, total, self.page, numfound, facet
|
|
@ -1 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
|
@ -1,9 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
def convert_to_list(v):
|
||||
if isinstance(v, list):
|
||||
return v
|
||||
if isinstance(v, tuple):
|
||||
return list(v)
|
||||
return [v, ]
|
170
lib/value.py
170
lib/value.py
|
@ -1,170 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from extensions import db
|
||||
from models.attribute import CIAttributeCache
|
||||
from lib.attribute import AttributeManager
|
||||
from lib.const import type_map
|
||||
from lib.const import TableMap
|
||||
|
||||
|
||||
class AttributeValueManager(object):
|
||||
"""
|
||||
manage CI attribute values
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _get_attr(self, key):
|
||||
"""key is one of attr_id, attr_name and attr_alias
|
||||
"""
|
||||
attr = CIAttributeCache.get(key)
|
||||
return attr
|
||||
|
||||
def _get_attr_values(self, fields, ci_id,
|
||||
ret_key="name",
|
||||
uniq_key=None,
|
||||
use_master=False):
|
||||
res = dict()
|
||||
for field in fields:
|
||||
attr = CIAttributeCache.get(field)
|
||||
if not attr:
|
||||
current_app.logger.warn('attribute %s not found' % field)
|
||||
return res
|
||||
table = TableMap(attr_name=attr.attr_name).table
|
||||
if use_master:
|
||||
rs = db.session().using_bind("master").query(
|
||||
table.value).filter_by(ci_id=ci_id).filter_by(
|
||||
attr_id=attr.attr_id)
|
||||
else:
|
||||
rs = db.session.query(table.value).filter_by(
|
||||
ci_id=ci_id).filter_by(attr_id=attr.attr_id)
|
||||
field_name = getattr(attr, "attr_{0}".format(ret_key))
|
||||
try:
|
||||
if attr.is_multivalue:
|
||||
if attr.value_type == 'datetime':
|
||||
res[field_name] = [datetime.datetime.strftime(
|
||||
x.value, '%Y-%m-%d %H:%M:%S') for x in rs.all()]
|
||||
else:
|
||||
res[field_name] = [x.value for x in rs.all()]
|
||||
else:
|
||||
x = rs.first()
|
||||
if x:
|
||||
if attr.value_type == 'datetime':
|
||||
res[field_name] = datetime.datetime.strftime(
|
||||
rs.first().value, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
res[field_name] = rs.first().value
|
||||
else:
|
||||
res[field_name] = None
|
||||
except AttributeError as e:
|
||||
current_app.logger.warn("get ci by id error, {0}".format(e))
|
||||
if attr.is_multivalue:
|
||||
res[field_name] = list()
|
||||
else:
|
||||
res[field_name] = ""
|
||||
if uniq_key is not None and attr.attr_id == uniq_key.attr_id \
|
||||
and rs.first() is not None:
|
||||
res['unique'] = uniq_key.attr_name
|
||||
return res
|
||||
|
||||
def _validate(self, attr, value, table, ci_id):
|
||||
converter = type_map.get("converter").get(attr.value_type)
|
||||
try:
|
||||
v = converter(value)
|
||||
except ValueError:
|
||||
return False, "attribute value {0} converter fail".format(value)
|
||||
if attr.is_choice:
|
||||
choice_list = AttributeManager()._get_choice_value(
|
||||
attr.attr_id, attr.value_type)
|
||||
if v not in choice_list:
|
||||
return False, "{0} is not existed in choice values".format(
|
||||
value)
|
||||
elif attr.is_uniq:
|
||||
old_value = db.session.query(table.attr_id).filter(
|
||||
table.attr_id == attr.attr_id).filter(
|
||||
table.value == v).filter(table.ci_id != ci_id).first()
|
||||
if old_value is not None:
|
||||
return False, "attribute {0} value {1} must be unique".format(
|
||||
attr.attr_name, value)
|
||||
return True, v
|
||||
|
||||
def add_attr_value(self, key, value, ci_id, ci_type,
|
||||
_no_attribute_policy="ignore", ci_existed=False):
|
||||
"""key is one of attr_id, attr_name and attr_alias
|
||||
"""
|
||||
attr = self._get_attr(key)
|
||||
if attr is None:
|
||||
if _no_attribute_policy == 'ignore':
|
||||
return True, None
|
||||
if _no_attribute_policy == 'reject':
|
||||
return False, 'attribute {0} not exist'.format(key)
|
||||
table, old_value, old_value_table = TableMap(
|
||||
attr_name=attr.attr_name).table, None, None
|
||||
if ci_existed:
|
||||
old_value_table = db.session.query(table).filter(
|
||||
table.attr_id == attr.attr_id).filter(
|
||||
table.ci_id == ci_id).first()
|
||||
if old_value_table is not None:
|
||||
old_value = old_value_table.value
|
||||
if not value and ci_existed:
|
||||
db.session.query(table).filter(
|
||||
table.attr_id == attr.attr_id).filter(
|
||||
table.ci_id == ci_id).delete()
|
||||
if old_value:
|
||||
return True, (attr.attr_id, "delete", old_value, None)
|
||||
else:
|
||||
return True, None
|
||||
elif not value:
|
||||
return True, None
|
||||
if not attr.is_multivalue:
|
||||
ret, res = self._validate(attr, value, table, ci_id)
|
||||
if not ret:
|
||||
return False, res
|
||||
value_table = table()
|
||||
if ci_existed: # for history
|
||||
old = db.session.query(table).filter(
|
||||
table.attr_id == attr.attr_id).filter(
|
||||
table.value == value).filter(
|
||||
table.ci_id == ci_id).first()
|
||||
if old is not None:
|
||||
return True, None
|
||||
elif old_value_table:
|
||||
value_table = old_value_table
|
||||
value_table.ci_id = ci_id
|
||||
value_table.attr_id = attr.attr_id
|
||||
value_table.value = res
|
||||
db.session.add(value_table)
|
||||
elif attr.is_multivalue:
|
||||
if ci_existed:
|
||||
db.session.query(table).filter(
|
||||
table.attr_id == attr.attr_id).filter(
|
||||
table.ci_id == ci_id).delete()
|
||||
|
||||
for v in value.strip().split(","):
|
||||
ret, res = self._validate(attr, v, table, ci_id)
|
||||
if not ret:
|
||||
return False, res
|
||||
value_table = table()
|
||||
value_table.ci_id = ci_id
|
||||
value_table.attr_id = attr.attr_id
|
||||
value_table.value = res
|
||||
db.session.add(value_table)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(
|
||||
"add attribute value is error, {0}".format(str(e)))
|
||||
return False, "add attribute value is error, {0}".format(str(e))
|
||||
if ci_existed:
|
||||
if old_value != value:
|
||||
return True, (attr.attr_id, "update", old_value, value)
|
||||
else:
|
||||
return True, None
|
||||
return True, (attr.attr_id, "add", None, value)
|
77
manage.py
77
manage.py
|
@ -1,77 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from flask import jsonify
|
||||
from flask import make_response
|
||||
from flask.ext.script import Manager
|
||||
from flask.ext.script import prompt_bool
|
||||
from flask.ext.celery import install_commands as install_celery_command
|
||||
|
||||
from __init__ import make_app
|
||||
from extensions import db
|
||||
from gunicornserver import GunicornServer
|
||||
from lib.exception import InvalidUsageError
|
||||
|
||||
|
||||
app = make_app('config.cfg')
|
||||
|
||||
|
||||
@app.errorhandler(InvalidUsageError)
|
||||
def handle_invalid_usage(error):
|
||||
response = jsonify(error.to_dict())
|
||||
response.status_code = error.status_code
|
||||
return response
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return make_response(jsonify({'message': error.description}), 404)
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request(error):
|
||||
return make_response(jsonify({'message': error.description}), 400)
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
def auth_lack(error):
|
||||
return make_response(jsonify({'message': error.description}), 401)
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
def exception_403(error):
|
||||
return make_response(jsonify({'message': error.description}), 403)
|
||||
|
||||
|
||||
@app.errorhandler(405)
|
||||
def exception_405(error):
|
||||
return make_response(jsonify({'message': error.description}), 405)
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error(error):
|
||||
return make_response(jsonify({"message": error.description}), 500)
|
||||
|
||||
|
||||
manager = Manager(app)
|
||||
|
||||
install_celery_command(manager)
|
||||
|
||||
|
||||
@manager.command
|
||||
def db_setup():
|
||||
"create all database tables"
|
||||
db.create_all()
|
||||
|
||||
|
||||
@manager.command
|
||||
def db_dropall():
|
||||
"drop all databse tables"
|
||||
if prompt_bool("Are you sure ? You will lose all your data !"):
|
||||
db.drop_all()
|
||||
|
||||
|
||||
manager.add_command("run", GunicornServer())
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run(default_command="runserver")
|
|
@ -1,13 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
def row2dict(row):
|
||||
d = dict()
|
||||
for c in row.__table__.columns:
|
||||
if not isinstance(getattr(row, c.name),
|
||||
(basestring, long, int, float, list, tuple, dict)) \
|
||||
and getattr(row, c.name):
|
||||
d[c.name] = getattr(row, c.name).strftime("%Y-%m-%d %H:%M:%S")
|
||||
elif c.name not in ("password", "secret"):
|
||||
d[c.name] = getattr(row, c.name)
|
||||
return d
|
|
@ -1,86 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from extensions import db, cache
|
||||
|
||||
|
||||
class CIAttribute(db.Model):
|
||||
__tablename__ = "ci_attributes"
|
||||
|
||||
attr_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
attr_name = db.Column(db.String(32), nullable=False, unique=True)
|
||||
attr_alias = db.Column(db.String(32), nullable=False, unique=True)
|
||||
value_type = db.Column(
|
||||
db.Enum("int", "float", "text", "datetime", name='value_type'),
|
||||
default="text",
|
||||
nullable=False)
|
||||
is_choice = db.Column(db.Boolean, default=False)
|
||||
is_multivalue = db.Column(db.Boolean, default=False)
|
||||
is_uniq = db.Column(db.Boolean, default=False)
|
||||
is_index = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
||||
class IntegerChoice(db.Model):
|
||||
__tablename__ = 'choice_integers'
|
||||
|
||||
choice_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
attr = db.relationship("CIAttribute", backref="choice_integers")
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
|
||||
class FloatChoice(db.Model):
|
||||
__tablename__ = 'choice_floats'
|
||||
|
||||
choice_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
attr = db.relationship("CIAttribute", backref="choice_floats")
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
|
||||
class TextChoice(db.Model):
|
||||
__tablename__ = 'choice_texts'
|
||||
|
||||
choice_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
attr = db.relationship("CIAttribute", backref="choice_texts")
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
|
||||
|
||||
class CIAttributeCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
attr = cache.get('Field::Name::%s' % key) or \
|
||||
cache.get('Field::ID::%s' % key) or \
|
||||
cache.get('Field::Alias::%s' % key)
|
||||
if attr is None:
|
||||
attr = db.session.query(CIAttribute).filter_by(
|
||||
attr_name=key).first() or \
|
||||
db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_id == key).first() or \
|
||||
db.session.query(CIAttribute).filter(
|
||||
CIAttribute.attr_alias == key).first()
|
||||
db.session.close()
|
||||
if attr is not None:
|
||||
CIAttributeCache.set(attr)
|
||||
return attr
|
||||
|
||||
@classmethod
|
||||
def set(cls, attr):
|
||||
cache.set('Field::ID::%s' % attr.attr_id, attr)
|
||||
cache.set('Field::Name::%s' % attr.attr_name, attr)
|
||||
cache.set('Field::Alias::%s' % attr.attr_alias, attr)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, attr):
|
||||
if cache.get('Field::ID::%s' % attr.attr_id):
|
||||
cache.delete('Field::ID::%s' % attr.attr_id)
|
||||
cache.delete('Field::Name::%s' % attr.attr_name)
|
||||
cache.delete('Field::Alias::%s' % attr.attr_alias)
|
19
models/ci.py
19
models/ci.py
|
@ -1,19 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
from extensions import db
|
||||
|
||||
|
||||
class CI(db.Model):
|
||||
__tablename__ = "cis"
|
||||
|
||||
ci_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = db.Column(db.String(32), nullable=False)
|
||||
type_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_types.type_id"),
|
||||
nullable=False)
|
||||
ci_type = db.relationship("CIType", backref="cis")
|
||||
status = db.Column(db.Enum("review", "validate", name="status"))
|
||||
created_time = db.Column(db.DateTime, default=datetime.datetime.now())
|
||||
heartbeat = db.Column(db.DateTime, default=datetime.datetime.now())
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from extensions import db
|
||||
|
||||
|
||||
class CIRelation(db.Model):
|
||||
__tablename__ = "ci_relations"
|
||||
cr_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
first_ci_id = db.Column(db.Integer,
|
||||
db.ForeignKey("cis.ci_id"),
|
||||
primary_key=True)
|
||||
second_ci_id = db.Column(db.Integer,
|
||||
db.ForeignKey("cis.ci_id"),
|
||||
primary_key=True)
|
||||
first_ci = db.relationship("CI",
|
||||
primaryjoin="CI.ci_id==CIRelation.first_ci_id")
|
||||
second_ci = db.relationship(
|
||||
"CI", primaryjoin="CI.ci_id==CIRelation.second_ci_id")
|
||||
relation_type = db.Column(
|
||||
db.Enum("connect", "deploy", "install", "contain",
|
||||
name="relation_type"), nullable=False)
|
||||
more = db.Column(db.Integer, db.ForeignKey("cis.ci_id"))
|
||||
|
||||
__table_args__ = (db.UniqueConstraint("first_ci_id", "second_ci_id",
|
||||
name="first_second_uniq"), )
|
|
@ -1,129 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from extensions import db
|
||||
from extensions import cache
|
||||
|
||||
|
||||
class CIType(db.Model):
|
||||
__tablename__ = "ci_types"
|
||||
|
||||
type_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
type_name = db.Column(db.String(32))
|
||||
type_alias = db.Column(db.String(32))
|
||||
uniq_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_attributes.attr_id"),
|
||||
nullable=False)
|
||||
uniq_key = db.relationship("CIAttribute", backref="ci_types")
|
||||
enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_attached = db.Column(db.Boolean, default=False, nullable=False)
|
||||
icon_url = db.Column(db.String(256))
|
||||
order = db.Column(db.SmallInteger, default=0, nullable=False)
|
||||
|
||||
|
||||
class CITypeAttribute(db.Model):
|
||||
__tablename__ = "type_attributes"
|
||||
|
||||
type_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_types.type_id"),
|
||||
primary_key=True)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_attributes.attr_id"),
|
||||
primary_key=True)
|
||||
is_required = db.Column(db.Boolean, default=False)
|
||||
order = db.Column(db.Integer, default=0)
|
||||
|
||||
__table_args__ = (db.UniqueConstraint("type_id", "attr_id",
|
||||
name="type_attr_uniq"), )
|
||||
|
||||
|
||||
class CITypeCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
ct = cache.get("CIType::ID::%s" % key) or \
|
||||
cache.get("CIType::Name::%s" % key)
|
||||
if ct is None:
|
||||
ct = db.session.query(CIType).filter(
|
||||
CIType.type_name == key).first() or \
|
||||
db.session.query(CIType).filter(CIType.type_id == key).first()
|
||||
if ct is not None:
|
||||
CITypeCache.set(ct)
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
def set(cls, ct):
|
||||
cache.set("CIType::Name::%s" % ct.type_name, ct)
|
||||
cache.set("CIType::ID::%d" % ct.type_id, ct)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ct = CITypeCache.get(key)
|
||||
if ct is not None:
|
||||
cache.delete("CIType::Name::%s" % ct.type_name)
|
||||
cache.delete("CIType::ID::%s" % ct.type_id)
|
||||
|
||||
|
||||
class CITypeSpecCache(object):
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
ct = cache.get("CITypeSPEC::ID::%d" % key)
|
||||
if ct is None:
|
||||
ct = db.session.query(CIType).filter(CIType.type_id == key).first()
|
||||
if ct is not None:
|
||||
CITypeSpecCache.set(ct)
|
||||
return ct
|
||||
|
||||
@classmethod
|
||||
def set(cls, ct):
|
||||
cache.set("CITypeSPEC::ID::%d" % ct.type_id, ct)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
ct = CITypeCache.get(key)
|
||||
if ct is not None:
|
||||
cache.delete("CITypeSPEC::ID::%d" % ct.type_id)
|
||||
|
||||
|
||||
class CITypeAttributeCache(object):
|
||||
"""
|
||||
key is type_id or type_name
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
if key is None:
|
||||
return
|
||||
if isinstance(key, basestring) and isinstance(key, unicode):
|
||||
key = unicode(key, 'utf8')
|
||||
citypes = cache.get("CITypeAttribute::Name::%s" % key) or \
|
||||
cache.get("CITypeAttribute::ID::%s" % key)
|
||||
if not citypes:
|
||||
citypes = db.session.query(CITypeAttribute).filter(
|
||||
CITypeAttribute.type_id == key).all()
|
||||
if citypes is None:
|
||||
ci_type = db.session.query(CIType).filter(
|
||||
CIType.type_name == key).first()
|
||||
if ci_type is not None:
|
||||
citypes = db.session.query(CITypeAttribute).filter_by(
|
||||
type_id=ci_type.type_id).all()
|
||||
if citypes is not None:
|
||||
CITypeAttributeCache.set(key, citypes)
|
||||
return citypes
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, values):
|
||||
citype = CITypeCache.get(key)
|
||||
if citype is not None:
|
||||
cache.set("CITypeAttribute::ID::%s" % citype.type_id, values)
|
||||
cache.set("CITypeAttribute::Name::%s" % citype.type_name, values)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
citype = CITypeCache.get(key)
|
||||
attrs = CITypeAttributeCache.get(key)
|
||||
if attrs is not None and citype:
|
||||
cache.delete("CITypeAttribute::ID::%s" % citype.type_id)
|
||||
cache.delete("CITypeAttribute::Name::%s" % citype.type_name)
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
from extensions import db
|
||||
|
||||
|
||||
class CITypeRelation(db.Model):
|
||||
__tablename__ = "ci_type_relations"
|
||||
|
||||
ctr_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
parent_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_types.type_id"),
|
||||
primary_key=True)
|
||||
parent = db.relationship(
|
||||
"CIType", primaryjoin="CIType.type_id==CITypeRelation.parent_id")
|
||||
child_id = db.Column(db.Integer,
|
||||
db.ForeignKey("ci_types.type_id"),
|
||||
primary_key=True)
|
||||
child = db.relationship(
|
||||
"CIType", primaryjoin="CIType.type_id==CITypeRelation.child_id")
|
||||
relation_type = db.Column(
|
||||
db.Enum("contain", "connect", "deploy", "install",
|
||||
name="relation_type"),
|
||||
default="contain")
|
||||
|
||||
__table_args__ = (db.UniqueConstraint("parent_id", "child_id",
|
||||
name="parent_child_uniq"), )
|
|
@ -1,117 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from extensions import db
|
||||
from sqlalchemy import Index
|
||||
|
||||
|
||||
class CIIndexValueInteger(db.Model):
|
||||
__tablename__ = "index_integers"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="index_integers")
|
||||
attr = db.relationship("CIAttribute", backref="index_integers")
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
__table_args__ = (Index("attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueFloat(db.Model):
|
||||
__tablename__ = "index_floats"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="index_floats")
|
||||
attr = db.relationship("CIAttribute", backref="index_floats")
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
__table_args__ = (Index("attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueText(db.Model):
|
||||
__tablename__ = "index_texts"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="index_texts")
|
||||
attr = db.relationship("CIAttribute", backref="index_texts")
|
||||
value = db.Column(db.String(128), nullable=False)
|
||||
|
||||
__table_args__ = (Index("attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIIndexValueDateTime(db.Model):
|
||||
__tablename__ = "index_datetime"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="index_datetime")
|
||||
attr = db.relationship("CIAttribute", backref="index_datetime")
|
||||
value = db.Column(db.DateTime, nullable=False)
|
||||
|
||||
__table_args__ = (Index("attr_value_index", "attr_id", "value"), )
|
||||
|
||||
|
||||
class CIValueInteger(db.Model):
|
||||
__tablename__ = "integers"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="integers")
|
||||
attr = db.relationship("CIAttribute", backref="integers")
|
||||
value = db.Column(db.Integer, nullable=False)
|
||||
|
||||
|
||||
class CIValueFloat(db.Model):
|
||||
__tablename__ = "floats"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="floats")
|
||||
attr = db.relationship("CIAttribute", backref="floats")
|
||||
value = db.Column(db.Float, nullable=False)
|
||||
|
||||
|
||||
class CIValueText(db.Model):
|
||||
__tablename__ = "texts"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="texts")
|
||||
attr = db.relationship("CIAttribute", backref="texts")
|
||||
value = db.Column(db.Text, nullable=False)
|
||||
|
||||
|
||||
class CIValueDateTime(db.Model):
|
||||
__tablename__ = "datetime"
|
||||
|
||||
value_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
ci_id = db.Column(db.Integer, db.ForeignKey('cis.ci_id'), nullable=False)
|
||||
attr_id = db.Column(db.Integer,
|
||||
db.ForeignKey('ci_attributes.attr_id'),
|
||||
nullable=False)
|
||||
ci = db.relationship("CI", backref="datetime")
|
||||
attr = db.relationship("CIAttribute", backref="datetime")
|
||||
value = db.Column(db.DateTime, nullable=False)
|
|
@ -1,50 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from extensions import db
|
||||
|
||||
|
||||
class OperationRecord(db.Model):
|
||||
__tablename__ = "records"
|
||||
|
||||
record_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uid = db.Column(db.Integer, db.ForeignKey('users.uid'), nullable=False)
|
||||
timestamp = db.Column(db.DateTime,
|
||||
nullable=False,
|
||||
default=datetime.datetime.now())
|
||||
origin = db.Column(db.String(32))
|
||||
ticket_id = db.Column(db.String(32))
|
||||
reason = db.Column(db.Text)
|
||||
|
||||
|
||||
class CIAttributeHistory(db.Model):
|
||||
__tablename__ = "histories"
|
||||
|
||||
h_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
operate_type = db.Column(db.Enum("add", "delete", "update",
|
||||
name="operate_type"))
|
||||
record_id = db.Column(db.Integer,
|
||||
db.ForeignKey("records.record_id"),
|
||||
nullable=False)
|
||||
ci_id = db.Column(db.Integer, nullable=False)
|
||||
attr_id = db.Column(db.Integer, nullable=False)
|
||||
old = db.Column(db.Text)
|
||||
new = db.Column(db.Text)
|
||||
|
||||
|
||||
class CIRelationHistory(db.Model):
|
||||
__tablename__ = "relation_histories"
|
||||
|
||||
rh_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
operate_type = db.Column(db.Enum("add", "delete", name="operate_type"))
|
||||
record_id = db.Column(db.Integer,
|
||||
db.ForeignKey("records.record_id"),
|
||||
nullable=False)
|
||||
first_ci_id = db.Column(db.Integer)
|
||||
second_ci_id = db.Column(db.Integer)
|
||||
relation_type = db.Column(
|
||||
db.Enum("connect", "deploy", "install", "contain",
|
||||
name="relation_type"))
|
||||
relation = db.Column(db.Integer, nullable=False)
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from extensions import db
|
||||
|
||||
|
||||
class UrlRecord(db.Model):
|
||||
|
||||
url_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
url = db.Column(db.String(64), nullable=False)
|
||||
response_time = db.Column(db.Float, nullable=False)
|
||||
is_ok = db.Column(db.Boolean, default=True)
|
||||
source = db.Column(db.String(32))
|
||||
remote_addr = db.Column(db.String(20))
|
||||
hits = db.Column(db.Integer)
|
||||
method = db.Column(db.String(5), default="GET")
|
||||
created_at = db.Column(db.DateTime, default=datetime.datetime.now())
|
|
@ -1,9 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from flask.ext.principal import RoleNeed, Permission
|
||||
|
||||
|
||||
admin = Permission(RoleNeed('admin'))
|
||||
auth = Permission(RoleNeed('authenticated'))
|
||||
null = Permission(RoleNeed('null'))
|
|
@ -1,14 +0,0 @@
|
|||
Flask==0.9
|
||||
Flask-Script==0.5.2
|
||||
Flask-Babel==0.8
|
||||
Flask-principal==0.3.5
|
||||
Flask-mail==0.7.4
|
||||
pymysql==0.5
|
||||
sqlalchemy==0.8.2
|
||||
Flask-sqlalchemy==0.16
|
||||
Flask-cache==0.9.2
|
||||
redis==2.7.2
|
||||
gunicorn==0.17.4
|
||||
celery==3.0.18
|
||||
flask-celery=2.4.3
|
||||
Jinja2==2.7.1
|
|
@ -0,0 +1,11 @@
|
|||
[flake8]
|
||||
ignore = D401,D202,E226,E302,E41
|
||||
max-line-length=120
|
||||
exclude = migrations/*
|
||||
max-complexity = 10
|
||||
|
||||
[isort]
|
||||
line_length=88
|
||||
multi_line_output=3
|
||||
skip=migrations/*
|
||||
include_trailing_comma=true
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue