├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── dns_updater
├── __init__.py
├── api
│ ├── __init__.py
│ └── worker.py
├── app.py
├── config.py
├── templates
│ └── healthcheck.html
├── updater.py
├── utils
│ ├── __init__.py
│ ├── handler.py
│ ├── tool_classes.py
│ └── updater_util.py
└── workers
│ ├── __init__.py
│ ├── view_worker.py
│ └── zone_worker.py
├── dnsdb
├── __init__.py
├── config.py
├── constant
│ ├── __init__.py
│ ├── constant.py
│ └── operation_type.py
├── deploy.py
├── migrate.py
├── static
│ ├── css
│ │ ├── app.5137ea317b6c1209dd442cc030992313.css
│ │ └── app.5137ea317b6c1209dd442cc030992313.css.map
│ ├── favicon.ico
│ ├── fonts
│ │ ├── element-icons.6f0a763.ttf
│ │ ├── fontawesome-webfont.674f50d.eot
│ │ ├── fontawesome-webfont.af7ae50.woff2
│ │ ├── fontawesome-webfont.b06871f.ttf
│ │ └── fontawesome-webfont.fee66e7.woff
│ ├── img
│ │ └── fontawesome-webfont.912ec66.svg
│ └── js
│ │ ├── app.8f6318bdebfe230b5a3f.js
│ │ ├── app.8f6318bdebfe230b5a3f.js.map
│ │ ├── manifest.cfdad16f39e54a963330.js
│ │ ├── manifest.cfdad16f39e54a963330.js.map
│ │ ├── vendor.35023790df232692a094.js
│ │ └── vendor.35023790df232692a094.js.map
├── templates
│ ├── healthcheck.html
│ └── index.html
└── view
│ ├── __init__.py
│ ├── api
│ └── __init__.py
│ └── web
│ ├── __init__.py
│ ├── auth.py
│ ├── config.py
│ ├── preview.py
│ ├── record.py
│ ├── root.py
│ ├── subnet.py
│ ├── user.py
│ ├── view_isp.py
│ └── view_record.py
├── dnsdb_command.py
├── dnsdb_common
├── __init__.py
├── dal
│ ├── __init__.py
│ ├── host_group_conf.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── deploy_history.py
│ │ ├── dns_colos.py
│ │ ├── dns_header.py
│ │ ├── dns_host.py
│ │ ├── dns_host_group.py
│ │ ├── dns_named_conf.py
│ │ ├── dns_record.py
│ │ ├── dns_serial.py
│ │ ├── dns_zone_conf.py
│ │ ├── ippool.py
│ │ ├── operation_log.py
│ │ ├── operation_log_detail.py
│ │ ├── operation_type.py
│ │ ├── subnets.py
│ │ ├── user.py
│ │ ├── view_acl_city_code.py
│ │ ├── view_acl_migrate_history.py
│ │ ├── view_acl_subnets.py
│ │ ├── view_config.py
│ │ ├── view_domain_name_state.py
│ │ ├── view_domain_names.py
│ │ ├── view_isp_status.py
│ │ ├── view_isps.py
│ │ ├── view_migrate_detail.py
│ │ ├── view_migrate_history.py
│ │ ├── view_records.py
│ │ ├── view_switch_ip_detail.py
│ │ └── view_switch_ip_history.py
│ ├── operation_log.py
│ ├── subnet_ip.py
│ ├── user.py
│ ├── view_isp_acl.py
│ ├── view_migrate.py
│ ├── view_record.py
│ └── zone_record.py
└── library
│ ├── IPy.py
│ ├── __init__.py
│ ├── api.py
│ ├── database.py
│ ├── decorators.py
│ ├── email_util.py
│ ├── exception.py
│ ├── flaskapp.py
│ ├── gunicorn_app.py
│ ├── local.py
│ ├── log.py
│ ├── param_validator.py
│ ├── singleton.py
│ ├── utils.py
│ └── validator.py
├── dnsdb_fe
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .postcssrc.js
├── __init__.py
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ ├── webpack.prod.conf.js
│ └── webpack.test.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ ├── prod.env.js
│ └── test.env.js
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── common
│ │ └── util.js
│ ├── components
│ │ ├── Menu.vue
│ │ ├── admin
│ │ │ └── Login.vue
│ │ ├── conf
│ │ │ ├── Conf.vue
│ │ │ ├── HeaderEdit.vue
│ │ │ ├── HostManager.vue
│ │ │ └── ZoneManager.vue
│ │ ├── log
│ │ │ └── DnsLog.vue
│ │ ├── preview
│ │ │ └── Preview.vue
│ │ ├── record
│ │ │ ├── Record.vue
│ │ │ ├── RecordManager.vue
│ │ │ └── SubnetManager.vue
│ │ ├── system
│ │ │ ├── System.vue
│ │ │ └── UserManager.vue
│ │ └── view
│ │ │ ├── AclManager.vue
│ │ │ ├── DomainManager.vue
│ │ │ ├── IpManager.vue
│ │ │ ├── IspManager.vue
│ │ │ ├── Migrate.vue
│ │ │ └── View.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── view.js
│ └── style.css
└── static
│ ├── .!83440!favicon.ico
│ ├── .!83441!favicon.ico
│ ├── .!83442!favicon.ico
│ ├── .!83443!favicon.ico
│ ├── .!83444!favicon.ico
│ ├── .gitkeep
│ └── favicon.ico
├── docs
├── Makefile
├── conf.py
├── index.rst
└── make.bat
├── etc
├── beta
│ ├── common.conf
│ ├── dnsdb-updater.conf
│ ├── dnsdb.conf
│ ├── supervisor-dnsdb.conf
│ └── supervisor-updater.conf
└── template
│ └── zone_header
├── requirements.txt
├── setup.cfg
├── setup.py
├── test-requirements.txt
└── tools
├── __init__.py
├── install_venv.py
├── install_venv_common.py
├── updater
├── mkrdns
└── pre_updater_start.sh
└── with_venv.sh
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: 便于贡献者复现和定位问题
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **问题描述**
11 |
12 |
13 | **环境配置**
14 |
15 |
16 | **复现步骤**
17 | 1.
18 | 2.
19 | 3.
20 |
21 | **实际输出结果**
22 |
23 |
24 | **期望输出结果**
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.egg-info
3 | *.venv
4 | AUTHORS
5 | ChangeLog
6 | build/*
7 | .idea
8 | */tmp/
9 |
10 | dnsdb_fe/.DS_Store
11 | dnsdb_fe/node_modules/
12 | dnsdb_fe/dist/
13 | dnsdb_fe/npm-debug.log*
14 | dnsdb_fe/yarn-debug.log*
15 | dnsdb_fe/yarn-error.log*
16 | dnsdb_fe/test/unit/coverage
17 | dnsdb_fe/test/e2e/reports
18 | dnsdb_fe/selenium-debug.log
19 | # Editor directories and files
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.db
25 |
26 | docs/_build/
27 | etc/dev/
28 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at open-dev@qunar.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 | # OpenDnsdb
5 |
6 | ## 项目主页
7 |
8 | OpenDnsdb 项目相关文档: [文档链接](../../wikis/home)
9 |
10 |
11 | ## 简介
12 |
13 | OpenDnsdb 是去哪儿网OPS团队开源的DNS管理系统,用于添加、修改、删除zones/records.
14 | 使用简单并可靠的方法管理View、ACL、网段等.
15 | 详尽的日志,便于审计.
16 |
17 | OpenDnsdb并不是一个DNS服务器,而是一个对现有DNS服务器的管理系统,提供Web管理UI以及命令行工具等.
18 |
19 | 对OpenDnsdb的操作,会生成DNS配置文件并同步给DNS服务器。也就是说OpenDnsdb的故障或不可用并不会对DNS服务本身造成任何影响.
20 |
21 | OpenDnsdb is an open source DNS management system for the OPS team. It is used to add, modify, and delete zones/records. Use simple and reliable methods to manage View, ACL, network segment, etc. Detailed logs for auditing.
22 |
23 | OpenDnsdb is not a DNS server, but a management system for existing DNS servers, providing Web management UI and command line tools.
24 |
25 | For OpenDnsdb operations, a DNS configuration file is generated and synchronized to the DNS server. That is to say, the failure or unavailability of OpenDnsdb does not affect the DNS service itself.
26 |
27 |
28 | ## 主要功能
29 |
30 | * 支持 Bind 9.
31 | * IP管理, 管理公司网段及ip,可以实现域名和ip的自动绑定
32 | * 域名管理, 域名的增、删、改、查.
33 | * View域名管理, view域名的增删改查、状态修改,view域名的迁移.
34 | * 配置管理, 管理zone文件,线上配置与数据库配置同步,修改配置自动完成部署.
35 | * 日志, 关键操作都有日志记录,并可通过页面进行查询,便于审查
36 | * 支持RESTful API, 支持Webhook.
37 | * 基于Python 3 开发, 支持Postgresql和SQLite.
38 |
39 |
40 | ## 应用结构
41 |
42 | * docs/
43 | 各种说明文档、手册, copyright/license等.
44 |
45 | * dnsdb_fe/
46 | web ui
47 |
48 | * tools/
49 | 同步脚本, 各种工具.
50 |
51 | * etc/
52 | 开发、测试环境的配置文件, 配置模板等.
53 |
54 | * dnsdb_command.py
55 | 数据库初始脚本
56 |
57 | * dnsdb/constant
58 | 常量设置,用到的正则匹配规则
59 |
60 |
61 | ## 安装手册
62 | * 环境 Python:3.6.8 pip:19.0.3
63 |
64 | * 支持的浏览器: chrome, Firefox
65 |
66 | * 安装virtualenv: `pip install virtualenv`
67 |
68 | * 项目克隆
69 |
70 | * 目录创建:`mkdir -p /var/log/open_dnsdb/`
71 |
72 | ```ini
73 | ; 日志目录配置: etc/beta/common.conf
74 | [log]
75 | log-dir = /var/log/open_dnsdb/
76 | ```
77 |
78 | * 切换到项目目录: `cd open_dnsdb `
79 |
80 | * 初始化项目python环境:
81 |
82 | ```bash
83 | $ python tools/install_venv.py -p /usr/bin/python3.6
84 | # 命令行参数:
85 | # -p 使用的python解释器版本, 确保使用virtualenv创建虚拟环境是python3.6+
86 | # 如果确认virtualenv命令是用python3安装的, 这个参数可以省略
87 | ```
88 |
89 | * 启用虚拟环境
90 |
91 | ```bash
92 | $ source .venv/bin/activate
93 | $ python -V # 确认python版本为3.6+
94 | ```
95 |
96 | * 初始化数据库
97 | * 数据库配置: etc/beta/common.conf
98 | ```ini
99 | [DB]
100 | connection=sqlite:////usr/local/open_dnsdb/dnsdb.db
101 | ```
102 | * touch /usr/local/open_dnsdb/dnsdb.db 新建数据文件
103 | * export FLASK_APP=dnsdb_command.py
104 | * export FLASK_ENV=beta
105 | * flask deploy (生成测试账号: test 密码:123456)
106 |
107 | * 生成程序控制脚本: tools/with_venv.sh python setup.py install
108 |
109 | * 安装supervisor用于管理python进程:
110 | * 安装: sudo pip install supervisor
111 | ```
112 | # python3版本supervisor安装
113 | pip install git+https://github.com/Supervisor/supervisor
114 | ```
115 |
116 | * 生成默认配置: echo_supervisord_conf > /etc/supervisord.conf
117 |
118 | * 修改配置文件 vim /etc/supervisord.conf
119 | ```ini
120 | [supervisord]
121 | childlogdir=/var/log/open_dnsdb ;日志文件位置
122 |
123 | [include]
124 | files = /etc/supervisor/conf.d/*.conf
125 | ```
126 |
127 | * mkdir -p /etc/supervisor/conf.d
128 |
129 | * 添加openDnsdb项目配配置:
130 | * dnsdb: cp etc/beta/supervisor-dnsdb.conf /etc/supervisor/conf.d/open-dnsdb.conf
131 | * updater(仅bind服务器需要): cp etc/beta/supervisor-updater.conf /etc/supervisor/conf.d/open-dnsdb-updater.conf
132 |
133 | * 启动: supervisord -c /etc/supervisord.conf
134 |
135 | * 查看是否启动成功: ps aux | grep supervisord
136 |
137 | * supervisorctl -c /etc/supervisord.conf
138 |
139 |
140 |
141 | ## ChangeLog
142 |
143 | * v0.2 - 2019-03-21
144 |
145 | **添加**
146 |
147 | 添加ipv6支持(暂不支持ipv6反解)
148 |
149 | **修改**
150 |
151 | 升级python版本,支持python3.6+
152 |
153 | ## 感谢
154 |
155 | 感谢以下同学对项目修改提出的宝贵建议:
156 |
157 | * [wss434631143](https://github.com/wss434631143)
158 |
159 |
--------------------------------------------------------------------------------
/dns_updater/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dns_updater/__init__.py
--------------------------------------------------------------------------------
/dns_updater/api/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from flask import (Blueprint)
4 | from oslo_config import cfg
5 |
6 | from dns_updater.api.worker import start_update_thread
7 | from dns_updater.utils.updater_util import send_alarm_email
8 | from dnsdb_common.library.decorators import authenticate
9 | from dnsdb_common.library.decorators import parse_params
10 | from dnsdb_common.library.decorators import resp_wrapper_json
11 |
12 | CONF = cfg.CONF
13 |
14 | bp = Blueprint('api', 'api')
15 |
16 |
17 | @bp.route('/notify_update/named', methods=['POST'])
18 | @authenticate
19 | @parse_params([dict(name='group_name', type=str, required=True, nullable=False),
20 | dict(name='group_conf_md5', type=str, required=True, nullable=False)])
21 | @resp_wrapper_json
22 | def update_named(group_name, group_conf_md5):
23 | if group_name != CONF.host_group:
24 | return send_alarm_email(
25 | u'Host %s group not match: local %s, param: %s' % (CONF.host_ip, CONF.host_group, group_name))
26 | start_update_thread('named.conf', group_conf_md5=group_conf_md5, group_name=group_name)
27 | return
28 |
29 |
30 | @bp.route('/notify_update', methods=['POST'])
31 | @authenticate
32 | @parse_params([dict(name='update_type', type=str, required=True, nullable=False),
33 | dict(name='group_name', type=str, required=True, nullable=False),
34 | dict(name='params', type=dict, required=True, nullable=False)])
35 | @resp_wrapper_json
36 | def update_conf(update_type, group_name, params):
37 | start_update_thread(update_type, group_name=group_name, **params)
38 | return
39 |
--------------------------------------------------------------------------------
/dns_updater/app.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import sys
5 |
6 | from flask import (Flask, render_template)
7 | from flask_restful import abort
8 | from jinja2 import TemplateNotFound
9 | from oslo_config import cfg
10 |
11 | from dns_updater.config import setup_config
12 | from dnsdb_common.library.gunicorn_app import GunicornApplication, number_of_workers
13 | from dnsdb_common.library.log import getLogger, setup
14 |
15 | CONF = cfg.CONF
16 | setup('dnsdb_updater_www')
17 | log = getLogger(__name__)
18 |
19 |
20 | def create_app():
21 | setup_config(sys.argv[1], 'dnsdb-updater', conf_dir=os.path.dirname(os.path.dirname(__file__)))
22 | log.error('This host belong to host group %s' % CONF.host_group)
23 |
24 | app = Flask(__name__)
25 | app.config['SECRET_KEY'] = CONF.etc.secret_key
26 |
27 | from dns_updater.utils.updater_util import check_necessary_options
28 | check_necessary_options()
29 |
30 | @app.route("/healthcheck.html", methods=['GET'])
31 | def health_check():
32 | try:
33 | return render_template('healthcheck.html')
34 | except TemplateNotFound:
35 | abort(404)
36 |
37 | @app.context_processor
38 | def default_context_processor():
39 | result = {'config': {'BASE_URL': CONF.web.base_url}}
40 | return result
41 |
42 | from dns_updater import api
43 | app.register_blueprint(api.bp, url_prefix='/api')
44 |
45 | return app
46 |
47 |
48 | application = create_app()
49 |
50 |
51 | def app_start():
52 | options = {
53 | 'workers': number_of_workers(),
54 | }
55 | for option in CONF.gunicorn:
56 | options[option] = CONF.gunicorn[option]
57 |
58 | GunicornApplication(application, options).run()
59 |
60 |
61 | if __name__ == '__main__':
62 | application.run(host='0.0.0.0', port=9000, debug=True)
63 |
--------------------------------------------------------------------------------
/dns_updater/config.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 |
5 | from oslo_config import cfg
6 |
7 | CONF = cfg.CONF
8 |
9 | CONF.register_opts([
10 | cfg.StrOpt('env'),
11 | cfg.StrOpt('secret_key'),
12 | cfg.StrOpt('log_dir'),
13 | cfg.StrOpt('tmp_dir'),
14 | cfg.StrOpt('pidfile'),
15 | cfg.StrOpt('backup_dir'),
16 | cfg.IntOpt('zone_update_interval'),
17 | cfg.StrOpt('allow_ip')
18 | ], 'etc')
19 |
20 | CONF.register_opts([
21 | cfg.StrOpt('dnsdbapi_url'),
22 | ], 'api')
23 |
24 | CONF.register_opts([
25 | cfg.StrOpt('log-dir'),
26 | cfg.StrOpt('log-file'),
27 | cfg.StrOpt('debug'),
28 | cfg.StrOpt('verbose'),
29 | ], 'log')
30 |
31 | CONF.register_opts([
32 | cfg.StrOpt('server'),
33 | cfg.StrOpt('port'),
34 | cfg.StrOpt('from_addr'),
35 | cfg.StrOpt('password', default=''),
36 | cfg.StrOpt('info_list'),
37 | cfg.StrOpt('alert_list'),
38 | ], 'MAIL')
39 |
40 | CONF.register_opts([
41 | cfg.StrOpt('base-url',
42 | default='/',
43 | help='The url prefix of this site.'),
44 | cfg.StrOpt('run-mode',
45 | default="werkzeug",
46 | choices=('gunicorn', 'werkzeug'),
47 | help="Run server use the specify mode."),
48 | cfg.StrOpt('bind',
49 | default='0.0.0.0',
50 | help='The IP address to bind'),
51 | cfg.IntOpt('port',
52 | default=8080,
53 | help='The port to listen'),
54 | cfg.BoolOpt('debug',
55 | default=False),
56 | ], 'web')
57 |
58 | CONF.register_opts([
59 | cfg.StrOpt('config',
60 | default=None,
61 | help='The path to a Gunicorn config file.'),
62 | cfg.StrOpt('bind',
63 | default='0.0.0.0:8888'),
64 | cfg.IntOpt('workers',
65 | default=0,
66 | help='The number of worker processes for handling requests'),
67 | cfg.BoolOpt('daemon',
68 | default=False,
69 | help='Daemonize the Gunicorn process'),
70 | cfg.StrOpt('accesslog',
71 | default=None,
72 | help='The Access log file to write to.'
73 | '"-" means log to stderr.'),
74 | cfg.StrOpt('loglevel',
75 | default='info',
76 | help='The granularity of Error log outputs.',
77 | choices=('debug', 'info', 'warning', 'error', 'critical')),
78 | cfg.BoolOpt('ignore-healthcheck-accesslog',
79 | default=False),
80 | cfg.IntOpt('timeout',
81 | default=30,
82 | help='Workers silent for more than this many seconds are '
83 | 'killed and restarted.'),
84 | cfg.StrOpt('worker-class',
85 | default='sync',
86 | help='The type of workers to use.',
87 | choices=('sync', 'eventlet', 'gevent', 'tornado'))
88 | ], 'gunicorn')
89 |
90 | CONF.register_opts([
91 | cfg.StrOpt('server'),
92 | cfg.StrOpt('port'),
93 | cfg.StrOpt('from_addr'),
94 | cfg.StrOpt('info_list'),
95 | cfg.StrOpt('alert_list'),
96 | ], 'MAIL')
97 |
98 | CONF.register_opts([
99 | cfg.StrOpt('named_dir'),
100 | cfg.StrOpt('zone_dir'),
101 | cfg.StrOpt('named_checkconf'),
102 | cfg.StrOpt('named_zonecheck'),
103 | cfg.StrOpt('mkrdns'),
104 | cfg.StrOpt('acl_dir'),
105 | cfg.StrOpt('rndc'),
106 | cfg.StrOpt('user', default='named'),
107 | cfg.StrOpt('group', default='named'),
108 | ], 'bind_default')
109 |
110 | CONF.register_opts([
111 | cfg.StrOpt('named_dir'),
112 | cfg.StrOpt('zone_dir'),
113 | cfg.StrOpt('named_checkconf'),
114 | cfg.StrOpt('rndc'),
115 | ], 'ViewSlave')
116 |
117 | CONF.register_opts([
118 | cfg.StrOpt('named_dir'),
119 | cfg.StrOpt('zone_dir'),
120 | cfg.StrOpt('acl_dir'),
121 | cfg.StrOpt('named_checkconf'),
122 | cfg.StrOpt('rndc'),
123 | ], 'ViewMaster')
124 |
125 | CONF.register_opts([
126 | cfg.StrOpt('named_dir'),
127 | cfg.StrOpt('zone_dir'),
128 | cfg.StrOpt('named_checkconf'),
129 | cfg.StrOpt('rndc'),
130 | ], 'Master')
131 |
132 |
133 | def setup_config(app_env, app_kind, conf_dir):
134 | common_config_file = os.path.join(conf_dir, "etc/{}/common.conf".format(app_env))
135 | default_config_files = [common_config_file]
136 | app_config_file = os.path.join(conf_dir, "etc/{}/{}.conf".format(app_env, app_kind))
137 | default_config_files.append(app_config_file)
138 | CONF(default_config_files=default_config_files, args=[])
139 |
140 | from dns_updater.utils.updater_util import (DnsdbApi, get_self_ip)
141 | CONF.host_ip = get_self_ip()
142 | CONF.host_group = DnsdbApi.get_host_group()['data']
143 | setattr(CONF, 'bind_conf', CONF.bind_default)
144 |
145 | if getattr(CONF, CONF.host_group, None):
146 | for k, v in CONF[CONF.host_group].items():
147 | if v is not None:
148 | setattr(CONF.bind_conf, k, v)
149 |
--------------------------------------------------------------------------------
/dns_updater/templates/healthcheck.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 | 200 ok
9 |
10 |
--------------------------------------------------------------------------------
/dns_updater/updater.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import fcntl
4 | import importlib
5 | import os
6 | import signal
7 | import sys
8 | import time
9 |
10 | from oslo_config import cfg
11 |
12 | from dns_updater.config import setup_config
13 | from dns_updater.utils.tool_classes import (QApplication)
14 | from dnsdb_common.library.exception import UpdaterErr
15 | from dnsdb_common.library.log import (getLogger, setup)
16 |
17 | setup('dnsdb_upater_zone')
18 | log = getLogger(__name__)
19 |
20 | fp = None
21 | CONF = cfg.CONF
22 |
23 |
24 | def _get_handler():
25 | mapping = {
26 | 'ViewMaster': 'dns_updater.workers.view_worker',
27 | 'default': 'dns_updater.workers.zone_worker'
28 | }
29 | zone_group = CONF.host_group
30 | if not zone_group.endswith('Master'):
31 | raise UpdaterErr('%s, slave group does not need to start updater.' % zone_group)
32 | # dnsdb请求zone信息
33 | module = importlib.import_module(mapping.get(zone_group, mapping['default']))
34 | return module.handler
35 |
36 |
37 | def _create_pid_file():
38 | global fp
39 | pidfile = CONF.etc.pidfile
40 | if pidfile is None:
41 | raise UpdaterErr("No pidfile option found in config file.")
42 | try:
43 | fp = open(pidfile, 'w')
44 | # LOCK_EX /* exclusive lock */
45 | # LOCK_NB * don't block when locking */
46 | fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
47 | fp.truncate()
48 | pid = os.getpid()
49 | fp.write(str(pid))
50 | fp.flush()
51 | except Exception as e:
52 | raise UpdaterErr("Failed to lock pidfile, perhaps named_updater is already running.")
53 |
54 |
55 | def _signal_handler(n=0, e=0):
56 | log.error("Shuting down normally.")
57 | sys.exit(0)
58 |
59 |
60 | class DnsdbUpdater(QApplication):
61 | name = "dnsdb-zone-updater"
62 | version = "1.0"
63 |
64 | def init_config(self, argv=None):
65 | setup_config(sys.argv[1], 'dnsdb-updater', conf_dir=os.path.dirname(os.path.dirname(__file__)))
66 | log.error('Host belong to group: %s' % CONF.host_group)
67 |
68 | def init_app(self): # 可选
69 | from dns_updater.utils.updater_util import check_necessary_options
70 | super(DnsdbUpdater, self).init_app()
71 | try:
72 | check_necessary_options()
73 | _create_pid_file()
74 | # SIGTERM software termination signal
75 | signal.signal(signal.SIGTERM, _signal_handler)
76 | except Exception as e:
77 | log.exception(e, exc_info=1)
78 | sys.exit(1)
79 |
80 | def main_loop(self):
81 | handler = _get_handler()
82 | while True:
83 | handler()
84 | time.sleep(CONF.etc.zone_update_interval)
85 |
86 |
87 | updater = DnsdbUpdater().make_entry_point()
88 |
89 | if __name__ == '__main__':
90 | updater()
91 |
--------------------------------------------------------------------------------
/dns_updater/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dns_updater/utils/__init__.py
--------------------------------------------------------------------------------
/dns_updater/utils/handler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from dns_updater.utils.updater_util import DnsdbApi
4 | from dnsdb_common.library.log import getLogger
5 | from dns_updater.utils.updater_util import send_alarm_email
6 | log = getLogger(__name__)
7 |
8 | from dns_updater.utils.tool_classes import GenericWorker
9 |
10 |
11 | class WatchZone(GenericWorker):
12 | def __init__(self, interval, queue_name):
13 | super(WatchZone, self).__init__(interval)
14 | self.queue_name = queue_name
15 | # todo 根据queue获取handle
16 | self.zone_handler = lambda x: x
17 |
18 | def handler(self):
19 | log.info('%s worker start' % self.queue_name)
20 | try:
21 | zones = DnsdbApi.get_update_zones(self.queue_name)
22 | if zones:
23 | self.zone_handler(zones)
24 | except Exception as e:
25 | log.exception(e)
26 | send_alarm_email(u"[CRITICAL] Failed to handle zone update of %s, because: %s" %
27 | (self.queue_name, e.message))
28 |
29 |
--------------------------------------------------------------------------------
/dns_updater/utils/tool_classes.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sys
4 | import threading
5 | from queue import Queue
6 |
7 | from oslo_config import cfg
8 |
9 | from dnsdb_common.library.log import getLogger, setup
10 | from dnsdb_common.library.exception import UpdaterErr
11 | from dnsdb_common.library.singleton import Singleton
12 | log = getLogger(__name__)
13 |
14 | setup('QApplication')
15 | CONF = cfg.CONF
16 |
17 |
18 | def parse_args(argv, version, default_config_files=None):
19 | cfg.CONF(argv[1:],
20 | project='qlib',
21 | version=version,
22 | default_config_files=default_config_files)
23 |
24 |
25 | class QApplication(Singleton):
26 | name = "QApplication"
27 | version = "0"
28 |
29 | def init_config(self, argv=None):
30 | is_argv_specified = False
31 | if isinstance(argv, (list, tuple)):
32 | is_argv_specified = True
33 | argv = [sys.argv[0]] + list(argv)
34 | if not is_argv_specified:
35 | argv = sys.argv
36 | parse_args(argv, self.version)
37 |
38 | def setup_logger(self):
39 | setup(self.name)
40 |
41 | def init_app(self):
42 | pass
43 |
44 | def on_shutdown(self):
45 | pass
46 |
47 | def main_loop(self):
48 | raise UpdaterErr('This function has not been implemented yet')
49 |
50 | def run(self):
51 | log.debug("app: %s, version: %s",
52 | self.name, self.version)
53 | log.debug("Initializing the application.")
54 | self.init_config()
55 | self.setup_logger()
56 | self.init_app()
57 | log.debug("Starting the application.")
58 | self.main_loop()
59 | self.on_shutdown()
60 | log.debug("Shutdown the application.")
61 |
62 | def make_entry_point(self):
63 | def wrap():
64 | self.run()
65 |
66 | return wrap
67 |
68 |
69 | class GenericWorker(threading.Thread):
70 | def __init__(self, interval):
71 | self.interval = interval
72 | self.event = threading.Event()
73 | self._is_running = False
74 | super(GenericWorker, self).__init__()
75 |
76 | def handler(self):
77 | raise NotImplementedError
78 |
79 | def run(self):
80 | log.info('%s thread start.' % self.__class__.__name__)
81 | self._is_running = True
82 | while self._is_running:
83 | self.handler()
84 | if self.event.wait(self.interval):
85 | break
86 |
87 |
88 | def stop(self):
89 | log.info('%s thread stop.' % self.__class__.__name__)
90 | self._is_running = False
91 | self.event.set()
92 |
93 |
94 | class ZoneUpdateHandler(threading.Thread):
95 | def __init__(self, queue, handler):
96 | super(ZoneUpdateHandler, self).__init__()
97 | self.event = threading.Event()
98 | self.lock = threading.Lock()
99 | self.zones_to_update = set()
100 | self.zones_queues = Queue()
101 | self.queue_name = queue
102 | self.daemon = True
103 | self.handler = handler
104 |
105 | def run(self):
106 | log.info('ZoneUpdateHandler thread start.')
107 | while not self.event.wait(0.1):
108 | zone = self.zones_queues.get()
109 | with self.lock:
110 | self.zones_to_update.remove(zone)
111 | self.handler(zone)
112 | log.error('ZoneUpdateHandler thread end.')
113 |
114 | def add_zones(self, zones):
115 | for zone in zones:
116 | with self.lock:
117 | if zone not in self.zones_to_update:
118 | self.zones_to_update.add(zone)
119 | self.zones_queues.put(zone)
120 | if not self.isAlive():
121 | log.error('ZoneUpdateHandler thread is stopped by accident.')
122 | raise Exception
123 |
124 |
--------------------------------------------------------------------------------
/dns_updater/workers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dns_updater/workers/__init__.py
--------------------------------------------------------------------------------
/dns_updater/workers/view_worker.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import datetime
4 |
5 | from dns_updater.utils.updater_util import *
6 |
7 | CONF = cfg.CONF
8 |
9 | TMP_DIR = CONF.etc.tmp_dir
10 | ZONE_DIR = CONF.bind_conf.zone_dir
11 |
12 |
13 | def _make_zone_file_from_dnsdb(zone):
14 | zone_info = DnsdbApi.get_zone_info(zone)['data']
15 | serial = zone_info["serial_num"]
16 | record_dict = zone_info["records"]
17 | header = zone_info['header']
18 |
19 | isp_file_dict = {}
20 | for isp, record_list in record_dict.items():
21 | tmp_dir = os.path.join(CONF.etc.tmp_dir, 'var/named', isp)
22 | make_dir(tmp_dir)
23 | tmp_zonefile_path = os.path.join(tmp_dir, zone)
24 | make_zone_file(zone, tmp_zonefile_path, serial, header, record_list)
25 | checkzone(zone, tmp_zonefile_path)
26 | isp_file_dict[isp] = {
27 | 'src': tmp_zonefile_path,
28 | 'dst': os.path.join(ZONE_DIR, isp, zone)
29 | }
30 | make_dir(os.path.join(ZONE_DIR, isp))
31 | return isp_file_dict
32 |
33 |
34 | def _backup_debug_file(debug_file):
35 | error_log = debug_file + datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
36 | shutil.copyfile(debug_file, error_log)
37 | return error_log
38 |
39 |
40 | def _copy_and_reload(isp_file_dict, zone):
41 | for isp, file_info in isp_file_dict.items():
42 | if os.system("cp -f %s %s >/dev/null 2>&1" % (file_info['src'], file_info['dst'])) != 0:
43 | raise UpdaterErr("Failed to copy file: src: %s, dst: %s" % (file_info['src'], file_info['dst']))
44 | backup_file(isp, file_info['src'])
45 |
46 | if CONF.etc.env != 'dev':
47 | rndc_debugfile = make_debugfile_path("rndc")
48 | if os.system("%s reload >%s 2>&1" % (CONF.bind_conf.rndc, rndc_debugfile)) != 0:
49 | error_log = backup_debug_file(rndc_debugfile)
50 | raise UpdaterErr("Failed to reload:%s, see %s." % (zone, error_log))
51 | log.info("Reloaded %s." % zone)
52 | return True
53 |
54 |
55 | def _send_all_changes_to_opsteam(isp_file_dict):
56 | diff_content = ''
57 | for isp, files in isp_file_dict.items():
58 | diff = get_file_diff(files['dst'], files['src'])
59 | if diff:
60 | diff_content += diff + "\n"*3
61 |
62 | send_zone_diff_email(diff_content)
63 |
64 |
65 | def handler():
66 | zone_list = DnsdbApi.get_update_zones(CONF.host_group)
67 | for zone_name in zone_list:
68 | try:
69 | isp_file_dict = _make_zone_file_from_dnsdb(zone_name)
70 | _send_all_changes_to_opsteam(isp_file_dict)
71 | if DnsdbApi.can_reload():
72 | _copy_and_reload(isp_file_dict, zone_name)
73 | DnsdbApi.update_zone_serial(zone_name)
74 | except Exception as e:
75 | log.exception(e)
76 | send_alarm_email(u'zone %s 更新失败\n原因: %s' % (zone_name, e))
77 |
--------------------------------------------------------------------------------
/dns_updater/workers/zone_worker.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from dns_updater.utils.updater_util import *
4 | import time
5 |
6 | TMP_DIR = CONF.etc.tmp_dir
7 | ZONE_DIR = CONF.bind_conf.zone_dir
8 |
9 |
10 | def _copy_named_files():
11 | zone_dir = ZONE_DIR
12 | tmp = os.path.join(TMP_DIR, 'var/named')
13 | make_dir(tmp)
14 | if os.system("cp -Rf %s/* %s/var/named/ >/dev/null 2>&1" % (zone_dir, TMP_DIR)) != 0:
15 | raise UpdaterErr("Failed to copy zone files to tmp_dir.")
16 | log.info("Copied named's files to %s.", TMP_DIR)
17 |
18 | # Return the output from mkrdns which will be used extract all modified PTR zones.
19 | def _build_PTR_records():
20 | debug_file = make_debugfile_path("mkrdns")
21 | if CONF.etc.env == 'dev':
22 | return debug_file
23 | if os.system("%s -rootdir %s %s >%s 2>&1" % (CONF.bind_conf.mkrdns,
24 | TMP_DIR, get_named_path(), debug_file)) != 0:
25 | error_log = backup_debug_file(debug_file)
26 | raise UpdaterErr("mkrdns did not return success, see %s." % error_log)
27 | log.info("PTR zones has been built.")
28 | return debug_file
29 |
30 |
31 | # Get all the PTR zones which should be reload.
32 | def _get_modified_PTR_zones(mkrdns_output, zone_file_dict):
33 | output, exit_code = run_command_with_code("grep 'Updating file' " + mkrdns_output, check_exit_code=False)
34 | if int(exit_code) != 0:
35 | log.info("No PTR zone needs to be reloaded.")
36 | return
37 | for buf in io.StringIO(output):
38 | ptn = re.match('^Updating file "(%s/var/named/[^"]*)".*' %
39 | TMP_DIR, buf)
40 | if ptn is not None:
41 | zone_file = ptn.group(1)
42 | zone = zone_file.split('/')[-1]
43 |
44 | zonename = _build_zonename_for_PTR_zone(zone)
45 | zone_file_dict[zonename] = {
46 | 'src': zone_file,
47 | 'dst': os.path.join(ZONE_DIR, zone)
48 | }
49 | checkzone(zonename, zone_file)
50 |
51 |
52 | def _build_zonename_for_PTR_zone(zone):
53 | tokens = zone.split(".")
54 | zonename = ""
55 | for j in range(len(tokens) - 2, -1, -1):
56 | zonename += tokens[j] + "."
57 | return zonename + "IN-ADDR.ARPA"
58 |
59 |
60 | def handler():
61 | zone_list = DnsdbApi.get_update_zones(CONF.host_group)
62 | log.info('zones to update: %s' % zone_list)
63 | for name in zone_list:
64 | zone_file_dict = {}
65 | try:
66 | _copy_named_files()
67 | current_zonefile_path = os.path.join(ZONE_DIR, name)
68 | if not check_file_exists(current_zonefile_path):
69 | raise UpdaterErr('Zone file not exist: %s' % current_zonefile_path)
70 |
71 | tmp_zonefile_path = make_zone_file_from_dnsdb(name)
72 | if not is_need_update_zone(tmp_zonefile_path, current_zonefile_path):
73 | continue
74 | checkzone(name, tmp_zonefile_path)
75 | zone_file_dict[name] = {
76 | 'src': tmp_zonefile_path,
77 | 'dst': current_zonefile_path
78 | }
79 |
80 | mkrdns_output = _build_PTR_records()
81 | _get_modified_PTR_zones(mkrdns_output, zone_file_dict)
82 | log.info('Update zones:\n %s' % (','.join(zone_file_dict.keys())))
83 | send_changes_to_opsteam(current_zonefile_path, tmp_zonefile_path)
84 | if DnsdbApi.can_reload():
85 | reload_and_backup_zones(zone_file_dict)
86 | DnsdbApi.update_zone_serial(name)
87 | log.info('update_zone_serial')
88 | time.sleep(1)
89 | except UpdaterErr as e:
90 | log.error(e.message)
91 | send_alarm_email(u'zone %s 更新失败\n原因: %s' % (name, e.message))
92 | except Exception as e:
93 | log.exception(e)
94 | send_alarm_email(u'zone %s 更新失败\n原因: %s' % (name, e))
95 |
--------------------------------------------------------------------------------
/dnsdb/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import sys
5 |
6 | from flask import Flask
7 | from flask_login import LoginManager
8 | from flask_restful import abort
9 | from oslo_config import cfg
10 |
11 | from dnsdb.config import Config, setup_config
12 | from dnsdb_common.dal import db
13 | from dnsdb_common.library.log import getLogger
14 | from dnsdb_common.library.gunicorn_app import GunicornApplication, number_of_workers
15 | from dnsdb_common.library.utils import make_tmp_dir
16 | from dnsdb_common.library.log import setup
17 |
18 | CONF = cfg.CONF
19 |
20 | setup('dnsdb')
21 | LOG = getLogger(__name__)
22 |
23 | def get_flask_app():
24 | app = Flask(__name__)
25 | app.config.from_object(CONF.flask_conf)
26 | db.init_app(app)
27 | return app
28 |
29 |
30 | def init_login_manager():
31 | login_manager = LoginManager()
32 | login_manager.session_protection = 'strong'
33 | login_manager.login_view = 'auth.login'
34 |
35 | from dnsdb_common.dal.models.user import User, AnonymousUser
36 | login_manager.anonymous_user = AnonymousUser
37 |
38 | @login_manager.user_loader
39 | def load_user(user_id):
40 | return User.query.get(int(user_id))
41 |
42 | return login_manager
43 |
44 |
45 | def createApp(app_env, app_kind, conf_dir):
46 | app = Flask(__name__)
47 | config_obj = Config(app_env, app_kind, conf_dir)
48 | CONF.flask_conf = config_obj
49 | app.config.from_object(config_obj)
50 |
51 | CONF.tmp_dir = make_tmp_dir('./tmp')
52 |
53 | db.init_app(app)
54 | login_manager = init_login_manager()
55 | login_manager.init_app(app)
56 |
57 | @login_manager.unauthorized_handler
58 | def unauthorized():
59 | return abort(401)
60 |
61 | LOG.info("dnsdb.started")
62 |
63 | @app.context_processor
64 | def default_context_processor():
65 | result = {'config': {'BASE_URL': CONF.web.base_url}}
66 | return result
67 |
68 | from dnsdb.view.web import root
69 | app.register_blueprint(root.bp, url_prefix='/')
70 |
71 | from dnsdb.view.web import auth
72 | app.register_blueprint(auth.bp, url_prefix='/web/auth')
73 |
74 | from dnsdb.view.web import user
75 | app.register_blueprint(user.bp, url_prefix='/web/user')
76 |
77 | from dnsdb.view.web import preview
78 | app.register_blueprint(preview.bp, url_prefix='/web/preview')
79 |
80 | from dnsdb.view.web import config
81 | app.register_blueprint(config.bp, url_prefix='/web/config')
82 |
83 | from dnsdb.view.web import subnet
84 | app.register_blueprint(subnet.bp, url_prefix='/web/subnet')
85 |
86 | from dnsdb.view.web import record
87 | app.register_blueprint(record.bp, url_prefix='/web/record')
88 |
89 | from dnsdb.view.web import view_isp
90 | app.register_blueprint(view_isp.bp, url_prefix='/web/view')
91 |
92 | from dnsdb.view.web import view_record
93 | app.register_blueprint(view_record.bp, url_prefix='/web/view')
94 |
95 | from dnsdb.view import api
96 | app.register_blueprint(api.bp, url_prefix='/api')
97 |
98 | return app
99 |
100 |
101 | def main():
102 | application = createApp(sys.argv[1], sys.argv[2], conf_dir=os.path.dirname(os.path.dirname(__file__)))
103 | options = {
104 | 'workers': number_of_workers(),
105 | }
106 | for option in CONF.gunicorn:
107 | options[option] = CONF.gunicorn[option]
108 |
109 | GunicornApplication(application, options).run()
110 |
111 |
112 | if __name__ == '__main__':
113 | application = createApp(app_env='dev', app_kind='dnsdb', conf_dir=os.path.dirname(os.path.abspath('.')))
114 | application.run(host='0.0.0.0', port=8888, debug=True)
115 |
--------------------------------------------------------------------------------
/dnsdb/config.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import sys
5 | from datetime import timedelta
6 |
7 | from oslo_config import cfg
8 |
9 | CONF = cfg.CONF
10 |
11 | CONF.register_opts([
12 | cfg.StrOpt('log-dir'),
13 | cfg.StrOpt('log-file'),
14 | cfg.StrOpt('debug'),
15 | cfg.StrOpt('verbose'),
16 | ], 'log')
17 |
18 | CONF.register_opts([
19 | cfg.StrOpt('connection'),
20 | cfg.StrOpt('data'),
21 | ], 'DB')
22 |
23 | CONF.register_opts([
24 | cfg.StrOpt('server'),
25 | cfg.StrOpt('port'),
26 | cfg.StrOpt('from_addr'),
27 | cfg.StrOpt('password', default=''),
28 | cfg.StrOpt('info_list'),
29 | cfg.StrOpt('alert_list'),
30 | ], 'MAIL')
31 |
32 | CONF.register_opts([
33 | cfg.StrOpt('allow_ip'),
34 | cfg.StrOpt('secret_key'),
35 | cfg.StrOpt('env'),
36 | cfg.StrOpt('header_template', default='../etc/template/zone_header')
37 | ], 'etc')
38 |
39 | CONF.register_opts([
40 | cfg.IntOpt('dnsupdater_port'),
41 | ], 'api')
42 |
43 | CONF.register_opts([
44 | cfg.StrOpt('acl_groups'),
45 | cfg.IntOpt('cname_ttl'),
46 | cfg.StrOpt('view_zone'),
47 | cfg.DictOpt('normal_view'),
48 | cfg.DictOpt('normal_cname'),
49 | ], 'view')
50 |
51 | CONF.register_opts([
52 | cfg.StrOpt('base-url',
53 | default='/',
54 | help='The url prefix of this site.'),
55 | cfg.StrOpt('run-mode',
56 | default="werkzeug",
57 | choices=('gunicorn', 'werkzeug'),
58 | help="Run server use the specify mode."),
59 | cfg.StrOpt('bind',
60 | default='0.0.0.0',
61 | help='The IP address to bind'),
62 | cfg.IntOpt('port',
63 | default=8080,
64 | help='The port to listen'),
65 | cfg.BoolOpt('debug',
66 | default=False),
67 | ], 'web')
68 |
69 | CONF.register_opts([
70 | cfg.StrOpt('config',
71 | default=None,
72 | help='The path to a Gunicorn config file.'),
73 | cfg.StrOpt('bind',
74 | default='127.0.0.1:8888'),
75 | cfg.IntOpt('workers',
76 | default=0,
77 | help='The number of worker processes for handling requests'),
78 | cfg.BoolOpt('daemon',
79 | default=False,
80 | help='Daemonize the Gunicorn process'),
81 | cfg.StrOpt('accesslog',
82 | default=None,
83 | help='The Access log file to write to.'
84 | '"-" means log to stderr.'),
85 | cfg.StrOpt('loglevel',
86 | default='info',
87 | help='The granularity of Error log outputs.',
88 | choices=('debug', 'info', 'warning', 'error', 'critical')),
89 | cfg.BoolOpt('ignore-healthcheck-accesslog',
90 | default=False),
91 | cfg.IntOpt('timeout',
92 | default=30,
93 | help='Workers silent for more than this many seconds are '
94 | 'killed and restarted.'),
95 | cfg.StrOpt('worker-class',
96 | default='sync',
97 | help='The type of workers to use.',
98 | choices=('sync', 'eventlet', 'gevent', 'tornado'))
99 | ], 'gunicorn')
100 |
101 |
102 | def setup_config(app_env, app_kind, conf_dir):
103 | if "--" in sys.argv:
104 | args = sys.argv[sys.argv.index("--") + 1:]
105 | else:
106 | args = []
107 |
108 | common_config_file = os.path.join(conf_dir, "etc/{}/common.conf".format(app_env))
109 | default_config_files = [common_config_file]
110 | app_config_file = os.path.join(conf_dir, "etc/{}/{}.conf".format(app_env, app_kind))
111 | default_config_files.append(app_config_file)
112 | CONF(default_config_files=default_config_files, args=args)
113 |
114 |
115 | class Config(object):
116 | def __init__(self, app_env, app_kind, conf_dir):
117 | # print 'conf_dir: ', conf_dir
118 | if "--" in sys.argv:
119 | args = sys.argv[sys.argv.index("--") + 1:]
120 | else:
121 | args = []
122 |
123 | common_config_file = os.path.join(conf_dir, "etc/{}/common.conf".format(app_env))
124 | default_config_files = [common_config_file]
125 | app_config_file = os.path.join(conf_dir, "etc/{}/{}.conf".format(app_env, app_kind))
126 | default_config_files.append(app_config_file)
127 | CONF(default_config_files=default_config_files, args=args)
128 |
129 | self.SECRET_KEY = os.environ.get('SECRET_KEY') or CONF.etc.secret_key
130 | self.SQLALCHEMY_DATABASE_URI = CONF.DB.connection
131 | self.SQLALCHEMY_TRACK_MODIFICATIONS = False
132 | self.PERMANENT_SESSION_LIFETIME = timedelta(days=1)
133 |
134 | # SECRET_KEY = os.environ.get('SECRET_KEY') or CONF.etc.secret_key
135 | # SQLALCHEMY_DATABASE_URI = CONF.DB.connection
136 | # SQLALCHEMY_TRACK_MODIFICATIONS = False
137 | # PERMANENT_SESSION_LIFETIME = timedelta(days=1)
138 |
--------------------------------------------------------------------------------
/dnsdb/constant/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/constant/__init__.py
--------------------------------------------------------------------------------
/dnsdb/constant/constant.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from oslo_config import cfg
4 | CONF = cfg.CONF
5 |
6 | RE_PATTERN = {
7 | 'username': r'(^[\w\.]{1-64}$)',
8 | 'email': r'^([\w-_]+(?:\.[\w-_]+)*)@((?:[a-z0-9]+(?:-[a-zA-Z0-9]+)*)+\.[a-z]{2,6})$',
9 | 'password': r'^(?=[\s\S]{6,9}$)(?=[\s\S]*[A-Z])(?=[\s\S]*[a-z])(?=[\s\S]*[0-9]).*'
10 | }
11 |
12 | NORMAL_TO_CNAME = CONF.view.normal_cname
13 | NORMAL_TO_VIEW = CONF.view.normal_view
14 | VIEW_ZONE = CONF.view.view_zone
15 |
--------------------------------------------------------------------------------
/dnsdb/constant/operation_type.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | operation_type_dict = {
4 | 'add_user': '用户新增',
5 | 'delete_user': '用户删除',
6 | 'update_user': '用户信息修改',
7 | 'add_host': '主机新增',
8 | 'delete_host': '主机删除',
9 | 'update_reload_status': '主机组reload修改',
10 | 'update_view_state': '状态修改',
11 | 'update_view_domain': '域名修改',
12 | 'add_view_domain': '域名新增',
13 | 'delete_view_domain': '域名删除',
14 | 'migrate_rooms': '机房迁移',
15 | 'recover_rooms': '机房恢复',
16 | # 'onekey_recover_rooms': '一键恢复',
17 | # 'switch_ip_to_aqb': 'IP高防切换',
18 | # 'switch_ip_from_aqb': 'IP高防恢复',
19 | # 'replace_ip': 'IP替换',
20 | # 'cancel_replace_ip': 'IP替换恢复',
21 | 'add_isp': 'ISP新增',
22 | 'delete_isp': 'ISP删除',
23 | 'update_zone_header': '头文件更新',
24 | 'update_named_zone': '配置更新zone',
25 | 'add_named_zone': '配置新增zone',
26 | 'delete_named_zone': '配置删除zone',
27 | 'update_named_conf_header': '配置更新named',
28 | 'conf_deploy': '配置部署',
29 | 'rename_subnet': '子网重命名',
30 | 'delete_subnet': '子网删除',
31 | 'add_subnet': '子网新增',
32 | 'manadd_record': '记录新增',
33 | 'delete_record': '记录删除',
34 | 'modify_record': '记录修改',
35 | 'autoadd_record': '记录自动绑定',
36 | 'acl_migration': 'acl运营商迁移',
37 | 'add_acl_subnet': 'acl网段新增',
38 | 'delete_acl_subnet': 'acl网段删除'
39 | # 'modify_isp_status': 'ISP状态修改'
40 | }
41 |
42 |
43 | filed_dict = {
44 | # user
45 | 'username': '用户名',
46 | 'role': '角色',
47 | 'email': '邮箱',
48 | # isp
49 | 'ename': '英文名',
50 | 'cname': '中文名',
51 | 'abbr': '别名',
52 | # view
53 | 'rooms': '机房',
54 | 'isps': '运营商'
55 | }
56 |
--------------------------------------------------------------------------------
/dnsdb/deploy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 | import threading
5 | import time
6 |
7 | from flask import Flask
8 | from oslo_config import cfg
9 |
10 | from dnsdb_common.dal import db
11 | from dnsdb_common.dal.operation_log import OperationLogDal
12 | from dnsdb_common.library.api import DnsUpdaterApi
13 | from dnsdb_common.library.exception import DnsdbException
14 | from dnsdb_common.library.log import setup, getLogger
15 |
16 | setup('dnsdb')
17 | log = getLogger(__name__)
18 |
19 | CONF = cfg.CONF
20 |
21 |
22 | def get_flask_app(flask_conf):
23 | app = Flask(__name__)
24 | app.config.from_object(flask_conf)
25 | db.init_app(app)
26 | return app
27 |
28 |
29 | class DeployException(DnsdbException):
30 | def __init__(self, message='Notify deploy conf file error', errcode=500, detail=None, msg_ch=u''):
31 | super(DeployException, self).__init__(message, errcode, detail, msg_ch)
32 |
33 |
34 | class DeployThread(threading.Thread):
35 | def __init__(self, job_id, is_retry=False):
36 | super(DeployThread, self).__init__()
37 | self.is_retry = is_retry
38 | self.state = 'wait'
39 | self.job_id = job_id
40 | self.done_host = []
41 | self.app = get_flask_app(CONF.flask_conf)
42 | self.deploy_info = {}
43 | self.deploy_type = ''
44 | self.expire = 120 # 任务超时时间2分钟
45 | self.unfinished = {}
46 | self.notify_failed = []
47 |
48 | def check_normal_update(self):
49 | time.sleep(self.expire)
50 | job = OperationLogDal.get_deploy_job(self.job_id)
51 | if job.op_result == 'start':
52 | OperationLogDal.update_opration_log(self.job_id, {'op_result': 'fail'})
53 |
54 | def update_named_conf(self):
55 | self.unfinished = {}
56 | for group_name, info in self.deploy_info.items():
57 | conf_md5 = info['md5']
58 | hosts = info['hosts']
59 | for host_ip in hosts:
60 | try:
61 | result = DnsUpdaterApi(host_ip=host_ip).notify_update(self.deploy_type, group_name,
62 | group_conf_md5=conf_md5,
63 | deploy_id=self.job_id)
64 | log.error('notify %s to update %s success, %s' % (host_ip, self.deploy_type, result))
65 | except Exception as e:
66 | log.error('notify %s to update %s failed, %s' % (host_ip, self.deploy_type, e))
67 | self.notify_failed.append(host_ip)
68 | self.unfinished[group_name] = hosts
69 |
70 | def update_acl(self):
71 | hosts = self.deploy_info.get('hosts', {})
72 | acl_files = self.deploy_info.get('acl_files', [])
73 | if not hosts or not acl_files:
74 | OperationLogDal.update_opration_log(self.job_id, {'op_result': 'ok'})
75 | for group_name, hosts in hosts.items():
76 | for host in hosts:
77 | try:
78 | result = DnsUpdaterApi(host_ip=host).notify_update(self.deploy_type, group_name,
79 | deploy_id=self.job_id, acl_files=acl_files)
80 | log.info('notify %s to update %s success, %s' % (host, self.deploy_type, result))
81 | except Exception as e:
82 | log.error('notify %s to update %s failed, %s' % (host, self.deploy_type, e))
83 | self.notify_failed.append(e)
84 |
85 | def init_zone(self):
86 | hosts = self.deploy_info.get('hosts', [])
87 | if not hosts:
88 | OperationLogDal.update_opration_log(self.job_id, {'op_result': 'ok'})
89 | group_name = self.deploy_info['group']
90 | zone = self.deploy_info['zone']
91 | for host in hosts:
92 | try:
93 | result = DnsUpdaterApi(host_ip=host).notify_update(self.deploy_type, group_name,
94 | deploy_id=self.job_id, zone=zone)
95 | log.info('notify %s to update %s success, %s' % (host, self.deploy_type, result))
96 | except Exception as e:
97 | log.error('notify %s to update %s failed, %s' % (host, self.deploy_type, e))
98 | self.notify_failed.append(e)
99 |
100 | def run(self):
101 | with self.app.app_context():
102 | job = OperationLogDal.get_deploy_job(self.job_id)
103 | if not job or job.op_result != 'wait':
104 | raise DeployException('No deploy job id=%s or job state=wait.' % self.job_id)
105 | OperationLogDal.update_opration_log(self.job_id, {
106 | 'op_result': 'start'
107 | })
108 | self.deploy_info = json.loads(job.op_before)
109 | self.deploy_type = job.op_domain
110 | if job.op_domain == 'named.conf':
111 | self.update_named_conf()
112 | elif job.op_domain == 'acl':
113 | self.update_acl()
114 | elif job.op_domain == 'zone':
115 | self.init_zone()
116 | if self.notify_failed:
117 | pass
118 | with self.app.app_context():
119 | self.check_normal_update()
120 |
121 |
122 | def start_deploy_job(user, deploy_info, conf_type, unfinished):
123 | job_id = OperationLogDal.create_deploy_job(user, deploy_info, conf_type, unfinished)
124 | thread = DeployThread(job_id)
125 | thread.start()
126 | return job_id
127 |
128 |
129 | def retry_deploy_job(job_id, username):
130 | if not OperationLogDal.reset_deploy_job(username, job_id):
131 | raise DeployException('Reset deploy job %s failed' % job_id)
132 | thread = DeployThread(job_id, is_retry=True)
133 | thread.start()
134 | return dict(code=0, data='ok')
135 |
--------------------------------------------------------------------------------
/dnsdb/migrate.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import threading
4 | import json
5 | from oslo_config import cfg
6 |
7 | from dnsdb import get_flask_app
8 | from dnsdb.constant.constant import NORMAL_TO_CNAME
9 | from dnsdb_common.dal.view_migrate import MigrateDal
10 | from dnsdb_common.library.log import setup, getLogger
11 | from dnsdb_common.library.exception import BadParam
12 |
13 | setup("dnsdb")
14 | log = getLogger(__name__)
15 |
16 | CONF = cfg.CONF
17 |
18 | def format_history(histories):
19 | history_list = []
20 | trans = MigrateDal.get_isp_trans()
21 | for history in histories:
22 | history_list.append({
23 | 'id': history.id,
24 | 'migrate_rooms': sorted(json.loads(history.migrate_rooms)),
25 | 'dst_rooms': sorted(json.loads(history.dst_rooms)),
26 | 'migrate_isps': sorted([trans[isp] for isp in json.loads(history.migrate_isps)]),
27 | 'cur': history.cur,
28 | 'all': history.all,
29 | 'state': history.state,
30 | 'rtx_id': history.rtx_id,
31 | 'update_at': history.updated_time.strftime('%Y-%m-%d %H:%M:%S')
32 | })
33 | return history_list
34 |
35 | def get_migrate_info(history_id):
36 | try:
37 | history_id = int(history_id)
38 | except Exception:
39 | raise BadParam('id should be int')
40 | return format_history([MigrateDal.get_history_info(history_id)])
41 |
42 | def migrate_rooms(src_rooms, dst_rooms, to_migrate_isps, username):
43 | history_id = MigrateDal.create_migrage_history(username)
44 |
45 | migrated_isp_rooms = MigrateDal.get_all_abnormal_isps(key='isp', value='room')
46 | to_migrate_dict = {isp: set(src_rooms) | migrated_isp_rooms.get(isp, set())
47 | for isp in to_migrate_isps}
48 |
49 | migrate_isp_domains = MigrateDal.list_migrate_domain_by_isp(to_migrate_dict, dst_rooms)
50 |
51 | has_migrate_domains = False
52 | for isp, migrate_domains_list in migrate_isp_domains.items():
53 | migrate_domains_list = [domain for domain in migrate_domains_list if domain['after_enabled_rooms']]
54 | if len(migrate_domains_list) == 0:
55 | continue
56 | has_migrate_domains = True
57 | MigrateDal.update_history_total(history_id, len(migrate_domains_list))
58 | m_thread = MigrateThread(username, history_id, migrate_domains_list)
59 | m_thread.start()
60 |
61 | if has_migrate_domains:
62 | MigrateDal.add_batch_abnormal_isp(username, to_migrate_dict)
63 | # send_alert_email("[FROM DNSDB]: 机房{}上运营商{}迁移到{}啦.".format(src_rooms, to_migrate_isps, dst_rooms))
64 | MigrateDal.update_history_by_id(history_id,
65 | migrate_rooms=json.dumps(src_rooms),
66 | migrate_isps=json.dumps(to_migrate_isps),
67 | dst_rooms=json.dumps(dst_rooms),
68 | migrate_info=json.dumps({}))
69 | else:
70 | MigrateDal.delete_history_by_id(history_id)
71 | raise BadParam("no domain can migrate, isp_rooms: %s"
72 | % to_migrate_dict, msg_ch=u'没有可迁移的机房')
73 |
74 | history_info = get_migrate_info(history_id)
75 | return history_info
76 |
77 | def list_migrate_history():
78 | return format_history(MigrateDal.get_last_few_history(limit=15))
79 |
80 |
81 | class MigrateThread(threading.Thread):
82 | def __init__(self, rtx_id, migrate_history_id, migrate_domain_list):
83 | super(MigrateThread, self).__init__()
84 | self.app = get_flask_app()
85 | self.step = 100
86 | self.rtx_id = rtx_id
87 | self.migrate_history_id = migrate_history_id
88 | self.migrate_domain_list = migrate_domain_list
89 |
90 | def run(self):
91 | with self.app.app_context():
92 | for i in range(0, len(self.migrate_domain_list), self.step):
93 | try:
94 | MigrateDal.migrate_domains(self.migrate_domain_list[i: i + self.step], self.migrate_history_id)
95 | except Exception as e:
96 | log.error('migrate rooms failed:%s' % e)
97 | MigrateDal.update_history_by_id(self.migrate_history_id, state='error')
98 | return
99 | # 更新serial
100 | MigrateDal.increase_serial_num(NORMAL_TO_CNAME.values())
101 |
--------------------------------------------------------------------------------
/dnsdb/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/favicon.ico
--------------------------------------------------------------------------------
/dnsdb/static/fonts/element-icons.6f0a763.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/fonts/element-icons.6f0a763.ttf
--------------------------------------------------------------------------------
/dnsdb/static/fonts/fontawesome-webfont.674f50d.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/fonts/fontawesome-webfont.674f50d.eot
--------------------------------------------------------------------------------
/dnsdb/static/fonts/fontawesome-webfont.af7ae50.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/fonts/fontawesome-webfont.af7ae50.woff2
--------------------------------------------------------------------------------
/dnsdb/static/fonts/fontawesome-webfont.b06871f.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/fonts/fontawesome-webfont.b06871f.ttf
--------------------------------------------------------------------------------
/dnsdb/static/fonts/fontawesome-webfont.fee66e7.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/static/fonts/fontawesome-webfont.fee66e7.woff
--------------------------------------------------------------------------------
/dnsdb/static/js/manifest.cfdad16f39e54a963330.js:
--------------------------------------------------------------------------------
1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,i){for(var u,a,f,s=0,l=[];s
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/dnsdb/templates/index.html:
--------------------------------------------------------------------------------
1 | Dns管理系统
--------------------------------------------------------------------------------
/dnsdb/view/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/view/__init__.py
--------------------------------------------------------------------------------
/dnsdb/view/api/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | from flask import (Blueprint)
5 |
6 | from dnsdb_common.dal.host_group_conf import HostGroupConfDal
7 | from dnsdb_common.dal.zone_record import ZoneRecordDal
8 | from dnsdb_common.dal.operation_log import OperationLogDal
9 | from dnsdb_common.dal.view_isp_acl import ViewIspAclDal
10 | from dnsdb_common.library.decorators import authenticate
11 | from dnsdb_common.library.decorators import parse_params
12 | from dnsdb_common.library.decorators import resp_wrapper_json
13 |
14 | bp = Blueprint('api', 'api')
15 |
16 |
17 | @bp.before_request
18 | @authenticate
19 | def require_authorization():
20 | pass
21 |
22 |
23 | @bp.route('/get/reload_status', methods=['GET'])
24 | @parse_params([dict(name='group_name', type=str, required=True, nullable=False)])
25 | @resp_wrapper_json
26 | def get_reload_status(group_name):
27 | return HostGroupConfDal.get_group_by_name(group_name).reload_status
28 |
29 |
30 | @bp.route('/get/host_group', methods=['GET'])
31 | @parse_params([dict(name='host_ip', type=str, required=True, nullable=False)])
32 | @resp_wrapper_json
33 | def get_host_group(host_ip):
34 | return HostGroupConfDal.get_group_by_ip(host_ip)
35 |
36 |
37 | @bp.route('/get/named_conf', methods=['GET'])
38 | @parse_params([dict(name='group_name', type=str, required=True, nullable=False)])
39 | @resp_wrapper_json
40 | def get_group_named(group_name):
41 | return HostGroupConfDal.build_complete_named_conf(group_name)
42 |
43 |
44 | @bp.route('/update/host_conf_md5', methods=['POST'])
45 | @parse_params([dict(name='host_ip', type=str, required=True, nullable=False),
46 | dict(name='host_conf_md5', type=str, required=True, nullable=False)])
47 | @resp_wrapper_json
48 | def update_host_conf_md5(host_ip, host_conf_md5):
49 | return HostGroupConfDal.update_host_conf_md5(host_ip, host_conf_md5)
50 |
51 |
52 | @bp.route('/get/acl_file', methods=['GET'])
53 | @parse_params([dict(name='acl_file', type=str, required=True, nullable=False)])
54 | @resp_wrapper_json
55 | def get_acl_file_content(acl_file):
56 | return ViewIspAclDal.get_acl_file_content(acl_file)
57 |
58 |
59 | @bp.route('/update/deploy_info', methods=['POST'])
60 | @parse_params([dict(name='deploy_id', type=int, required=True, nullable=False),
61 | dict(name='host', type=str, required=True, nullable=False),
62 | dict(name='is_success', type=bool, required=True, nullable=False),
63 | dict(name='msg', type=str, required=True, nullable=False)])
64 | @resp_wrapper_json
65 | def update_deploy_info(deploy_id, host, is_success, msg):
66 | OperationLogDal.update_deploy_info(deploy_id, host, is_success, msg)
67 |
68 |
69 | @bp.route('/get/update_zones', methods=['GET'])
70 | @parse_params([dict(name='group_name', type=str, required=True, nullable=False)])
71 | @resp_wrapper_json
72 | def get_update_zones(group_name):
73 | return ZoneRecordDal.get_zone_need_update(group_name)
74 |
75 |
76 | @bp.route('/get/zone_info', methods=['GET'])
77 | @parse_params([dict(name='zone_name', type=str, required=True, nullable=False)])
78 | @resp_wrapper_json
79 | def get_zone_info(zone_name):
80 | zone_info = ZoneRecordDal.get_zone_header(zone_name)
81 | zone_info['records'] = ZoneRecordDal.get_zone_records(zone_name)
82 | return zone_info
83 |
84 | @bp.route('/update/zone_serial', methods=['POST'])
85 | @parse_params([dict(name='zone_name', type=str, required=True, nullable=False)])
86 | @resp_wrapper_json
87 | def update_zone_serial(zone_name):
88 | return ZoneRecordDal.update_serial_num(zone_name)
89 |
--------------------------------------------------------------------------------
/dnsdb/view/web/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb/view/web/__init__.py
--------------------------------------------------------------------------------
/dnsdb/view/web/auth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | from dnsdb_common.dal.user import UserDal
5 | from dnsdb_common.library.decorators import resp_wrapper_json
6 | from dnsdb_common.library.exception import DnsdbException
7 | from dnsdb_common.library.exception import Unauthorized
8 | from flask import (Blueprint)
9 | from flask import request
10 | from flask_login import login_user, logout_user, login_required, current_user
11 |
12 | bp = Blueprint('auth', 'auth')
13 |
14 |
15 | @bp.route('/login', methods=['GET', 'POST'])
16 | @resp_wrapper_json
17 | def login():
18 | if request.method == 'POST':
19 | form = request.get_json(force=True)
20 | user = UserDal.get_user_info(username=form['username'])
21 | if user is not None and user.verify_password(form['password']):
22 | login_user(user, remember=True)
23 | return current_user.username
24 | raise DnsdbException('Invalid username or password.', msg_ch=u'账号或密码错误')
25 | else:
26 | raise Unauthorized()
27 |
28 |
29 | @bp.route('/logout', methods=['POST'])
30 | @login_required
31 | @resp_wrapper_json
32 | def logout():
33 | logout_user()
34 | raise Unauthorized()
35 |
36 |
37 | @bp.route("/logged_in_user", methods=['GET'])
38 | @login_required
39 | @resp_wrapper_json
40 | def logged_in_user():
41 | return current_user.username
42 |
--------------------------------------------------------------------------------
/dnsdb/view/web/preview.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | from flask import (Blueprint, redirect, url_for)
6 | from flask_login import current_user
7 |
8 | from dnsdb.constant.operation_type import operation_type_dict, filed_dict
9 | from dnsdb_common.dal.operation_log import OperationLogDal
10 | from dnsdb_common.dal.view_isp_acl import ViewIspAclDal
11 | from dnsdb_common.dal.view_migrate import MigrateDal
12 | from dnsdb_common.dal.view_record import ViewRecordDal
13 | from dnsdb_common.library.decorators import parse_params
14 | from dnsdb_common.library.decorators import resp_wrapper_json
15 | from dnsdb import deploy
16 |
17 | bp = Blueprint('preview', 'preview')
18 |
19 |
20 | @bp.before_request
21 | def require_authorization():
22 | if not current_user.is_authenticated:
23 | redirect(url_for('auth.login'))
24 |
25 |
26 | @bp.route('/list/operation_constant', methods=['GET'])
27 | @resp_wrapper_json
28 | def list_operationtype():
29 | return {'type_dict': operation_type_dict, 'filed_dict': filed_dict}
30 |
31 |
32 | @bp.route('/get/dns_log_detail', methods=['GET'])
33 | @parse_params([dict(name='id', type=int, required=True, nullable=False)])
34 | @resp_wrapper_json
35 | def get_dns_log_detail(id):
36 | OperationLogDal.get_log_detail(id)
37 |
38 |
39 | @bp.route('/list/operation_log', methods=['GET'])
40 | @parse_params([dict(name='page', type=int, required=True, nullable=False),
41 | dict(name='page_size', type=int, required=True, nullable=False),
42 | dict(name='start_time', type=str, required=False),
43 | dict(name='end_time', type=str, required=False),
44 | dict(name='domain', type=str, required=False),
45 | dict(name='type', type=str, required=False),
46 | dict(name='rtx_id', type=str, required=False),
47 | ])
48 | @resp_wrapper_json
49 | def list_operation_log(**kwargs):
50 | return OperationLogDal.list_operation_log(kwargs)
51 |
52 |
53 | @bp.route('retry_deploy_job', methods=['POST'])
54 | @parse_params([dict(name='deploy_id', type=int, required=True, nullable=False)], need_username=True)
55 | @resp_wrapper_json
56 | def retry_deploy_job(deploy_id, username):
57 | return deploy.retry_deploy_job(deploy_id, username)
58 |
59 |
60 | @bp.route('/get/previewinfo', methods=['GET'])
61 | @resp_wrapper_json
62 | def get_previewinfo():
63 | trans = MigrateDal.get_isp_trans()
64 | domain_count = ViewRecordDal.zone_domain_count()
65 | migrate_list = []
66 | histories = MigrateDal.get_migrated_history()
67 | for history in histories:
68 | migrate_list.append({
69 | 'migrate_rooms': sorted(json.loads(history.migrate_rooms)),
70 | 'dst_rooms': sorted(json.loads(history.dst_rooms)),
71 | 'migrate_isps': sorted([trans[isp] for isp in json.loads(history.migrate_isps)])
72 | })
73 |
74 | migrate_acl_subnet = ViewIspAclDal.get_migrate_subnet()
75 |
76 | return {'domain_count': domain_count,
77 | 'migrate': migrate_list,
78 | 'acl_migrate': migrate_acl_subnet}
79 |
--------------------------------------------------------------------------------
/dnsdb/view/web/root.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | from flask import (abort, Blueprint)
5 | from flask import render_template, url_for
6 | from flask_login import login_required
7 | from jinja2 import TemplateNotFound
8 |
9 | bp = Blueprint('root', 'root')
10 |
11 |
12 | @bp.route("/", methods=['GET'])
13 | @bp.route("/dnsdb/", methods=['GET'])
14 | @bp.route("/api/", methods=['GET'])
15 | def root(path=''):
16 | try:
17 | return render_template('index.html')
18 | except TemplateNotFound:
19 | abort(404)
20 |
21 |
22 | @bp.route("/index", methods=['GET'])
23 | @login_required
24 | def index(path=''):
25 | try:
26 | return render_template('index.html')
27 | except TemplateNotFound:
28 | abort(404)
29 |
30 |
31 | @bp.route("/healthcheck.html", methods=['GET'])
32 | def health_check():
33 | try:
34 | return render_template('healthcheck.html')
35 | except TemplateNotFound:
36 | abort(404)
37 |
--------------------------------------------------------------------------------
/dnsdb/view/web/subnet.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | import ipaddress
5 |
6 | from flask import (Blueprint)
7 | from flask_login import login_required
8 |
9 | from dnsdb_common.dal.subnet_ip import SubnetIpDal
10 | from dnsdb_common.library.decorators import add_web_opration_log
11 | from dnsdb_common.library.decorators import parse_params
12 | from dnsdb_common.library.decorators import resp_wrapper_json
13 | from dnsdb_common.library.exception import BadParam
14 |
15 | bp = Blueprint('subnet', 'subnet')
16 |
17 |
18 | @bp.before_request
19 | @login_required
20 | def require_authorization():
21 | pass
22 |
23 |
24 | def _is_valide_region(region):
25 | if len(region) > 64:
26 | raise BadParam('The length of region should <80', msg_ch=u'名称长度必须<80')
27 |
28 | if not re.match(r'^[a-zA-Z0-9_]+$', region):
29 | raise BadParam('region only a-z,A-Z,0-9,_ allowed.', msg_ch=u'名称中只能包含大小写字母、数字、下划线')
30 |
31 |
32 | def _validate_args(subnet, region, colo):
33 | if '/' not in subnet:
34 | raise BadParam('Invalid subnet.', msg_ch=u'请使用cidr格式的网段')
35 |
36 | _is_valide_region(region)
37 |
38 | if colo not in SubnetIpDal.get_colo_by_group('subnet'):
39 | raise BadParam('Invalid colo', msg_ch=u'请先配置机房')
40 |
41 | try:
42 | sub = ipaddress.ip_network(subnet)
43 | except Exception as e:
44 | raise BadParam('Invalid subnet: %s' % e, msg_ch=u'错误的网段格式')
45 |
46 | prefix = sub.prefixlen
47 | if sub.version == 4:
48 | if prefix < 16 or prefix > 32:
49 | raise BadParam('Invalid subnet.', msg_ch=u'IPv4掩码长度在[16-32]之间')
50 | else:
51 | if prefix < 64 or prefix > 128:
52 | raise BadParam('Invalid subnet.', msg_ch=u'IPv6掩码长度在[64-128]之间')
53 |
54 |
55 |
56 | def add_subnet_log(result, **kwargs):
57 | return kwargs['region'], {}, {'subnet': kwargs['subnet']}
58 |
59 |
60 | def delete_subnet_log(result, **kwargs):
61 | return kwargs['region'], {'subnet': kwargs['subnet']}, {}
62 |
63 |
64 | def rename_subnet_log(result, **kwargs):
65 | return kwargs['new_region'], {'region': kwargs['old_region']}, {'region': kwargs['new_region']}
66 |
67 |
68 | @bp.route('/add/subnet', methods=['POST'])
69 | @parse_params([dict(name='subnet', type=str, required=True, nullable=False),
70 | dict(name='region', type=str, required=True, nullable=False),
71 | dict(name='colo', type=str, required=True, nullable=False),
72 | dict(name='comment', type=str, required=False, nullable=False)],
73 | need_username=True)
74 | @resp_wrapper_json
75 | @add_web_opration_log('add_subnet', get_op_info=add_subnet_log)
76 | def add_subnet(subnet, region, colo, comment, username):
77 | _validate_args(subnet, region, colo)
78 | return SubnetIpDal.add_subnet(subnet, region, colo, comment, username)
79 |
80 |
81 | @bp.route('/delete', methods=['POST'])
82 | @parse_params([dict(name='subnet', type=str, required=True, nullable=False),
83 | dict(name='region', type=str, required=True, nullable=False)])
84 | @resp_wrapper_json
85 | @add_web_opration_log('delete_subnet', get_op_info=delete_subnet_log)
86 | def delete_subnet(subnet, region):
87 | return SubnetIpDal.delete_subnet(subnet, region)
88 |
89 |
90 | @bp.route('/rename_subnet', methods=['POST'])
91 | @parse_params([dict(name='old_region', type=str, required=True, nullable=False),
92 | dict(name='new_region', type=str, required=True, nullable=False)],
93 | need_username=True)
94 | @resp_wrapper_json
95 | @add_web_opration_log('rename_subnet', get_op_info=rename_subnet_log)
96 | def rename_subnet(old_region, new_region, username):
97 | _is_valide_region(new_region)
98 | return SubnetIpDal.rename_subnet(old_region, new_region, username)
99 |
100 |
101 | @bp.route('/get/subnet_colos', methods=['GET'])
102 | @resp_wrapper_json
103 | def get_subnet_colos():
104 | return SubnetIpDal.get_colo_by_group('subnet')
105 |
106 |
107 | @bp.route('get/subnet_ip', methods=['GET'])
108 | @parse_params([dict(name='region', type=str, required=True, nullable=False)])
109 | @resp_wrapper_json
110 | def get_subnet_ip(region):
111 | return SubnetIpDal.get_subnet_ip(region)
112 |
113 |
114 | @bp.route('/list/region', methods=['GET'])
115 | @resp_wrapper_json
116 | def list_region():
117 | return SubnetIpDal.list_region()
118 |
119 |
120 | @bp.route('/get/region_by_ip', methods=['GET'])
121 | @parse_params([dict(name='ip', type=str, required=True, nullable=False)])
122 | @resp_wrapper_json
123 | def get_region_by_ip(ip):
124 | return [SubnetIpDal.get_region_by_ip(ip)]
125 |
126 |
127 | @bp.route('/get/region_by_name', methods=['GET'])
128 | @parse_params([dict(name='region', type=str, required=True, nullable=False)])
129 | @resp_wrapper_json
130 | def get_region_by_name(region):
131 | return SubnetIpDal.get_region_by_name_like(region)
132 |
--------------------------------------------------------------------------------
/dnsdb/view/web/user.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 |
5 | from flask import (Blueprint)
6 | from flask_login import current_user, login_required
7 |
8 | from dnsdb.constant.constant import RE_PATTERN
9 | from dnsdb_common.dal.user import UserDal
10 | from dnsdb_common.library.decorators import add_web_opration_log
11 | from dnsdb_common.library.decorators import parse_params
12 | from dnsdb_common.library.decorators import resp_wrapper_json
13 | from dnsdb_common.library.exception import BadParam
14 |
15 | bp = Blueprint('user', 'user')
16 |
17 |
18 | def _is_valid_user(user):
19 | ptn = re.search(RE_PATTERN['username'], user)
20 | if ptn is None:
21 | return False
22 | if len(ptn.groups()) == 0:
23 | return False
24 | return ptn.group(1) == user
25 |
26 |
27 | def get_add_info(result, **kwargs):
28 | op_after = kwargs.copy()
29 | op_after.pop('password', None)
30 | role_id = op_after.pop('role_id')
31 | op_after['role'] = UserDal.get_role_name(role_id)
32 | return kwargs['username'], {}, op_after
33 |
34 |
35 | def get_update_info(result, **kwargs):
36 | op_after = kwargs.copy()
37 | op_after.pop('password', None)
38 | role_id = op_after.pop('role_id')
39 | op_after['role'] = UserDal.get_role_name(role_id)
40 | return kwargs['username'], {}, op_after
41 |
42 |
43 | def get_delete_info(result, **kwargs):
44 | username = kwargs['username']
45 | return username, result, {}
46 |
47 |
48 | @bp.route('/roles', methods=['GET'])
49 | @login_required
50 | @resp_wrapper_json
51 | def get_roles():
52 | return UserDal.get_roles()
53 |
54 |
55 | @bp.route('/get', methods=['GET'])
56 | @login_required
57 | @parse_params([dict(name='username', type=str, required=False, nullable=False)])
58 | @resp_wrapper_json
59 | def get_user(username):
60 | user = UserDal.get_user_info(username=username)
61 | if user is None:
62 | return []
63 | return [UserDal.get_user_info(username=username).json_serialize()]
64 |
65 |
66 | @bp.route('/list', methods=['GET'])
67 | @login_required
68 | @parse_params([dict(name='page', type=int, required=True, nullable=False),
69 | dict(name='page_size', type=int, required=True, nullable=False),
70 | dict(name='role_id', type=str, required=False, nullable=False)])
71 | @resp_wrapper_json
72 | def list_user(**kwargs):
73 | return UserDal.list_user(**kwargs)
74 |
75 |
76 | @bp.route('/add', methods=['POST'])
77 | @login_required
78 | @parse_params([dict(name='username', type=str, required=True, nullable=False),
79 | dict(name='email', type=str, required=True, nullable=False),
80 | dict(name='password', type=str, required=True, nullable=False),
81 | dict(name='role_id', type=int, required=True, nullable=False)])
82 | @resp_wrapper_json
83 | @add_web_opration_log('add_user', get_op_info=get_add_info)
84 | def add_user(username, email, password, role_id):
85 | UserDal.add_user(username, email, password, role_id)
86 |
87 |
88 | @bp.route('/delete', methods=['POST'])
89 | @login_required
90 | @parse_params([dict(name='username', type=str, required=True, nullable=False)])
91 | @resp_wrapper_json
92 | @add_web_opration_log('delete_user', get_op_info=get_delete_info)
93 | def delete_user(username):
94 | if username == current_user.username:
95 | raise BadParam('cannot delete yourself')
96 | user = UserDal.get_user_info(username=username)
97 | if not user:
98 | raise BadParam('No such user with name: %s' % username)
99 | result = user.json_serialize(include=('username', 'email', 'role'))
100 | UserDal.delete_user(username)
101 | return result
102 |
--------------------------------------------------------------------------------
/dnsdb/view/web/view_isp.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from flask import (Blueprint, request)
4 | from flask_login import login_required
5 |
6 | from dnsdb_common.dal.view_isp_acl import ViewIspAclDal
7 | from dnsdb_common.library.decorators import add_web_opration_log
8 | from dnsdb_common.library.decorators import parse_params
9 | from dnsdb_common.library.decorators import resp_wrapper_json
10 | from dnsdb_common.library.exception import BadParam
11 | from dnsdb_common.library.validator import valid_string
12 |
13 | bp = Blueprint('view', 'view')
14 |
15 |
16 | @bp.before_request
17 | @login_required
18 | def require_authorization():
19 | pass
20 |
21 |
22 | def _validate_args(param_dict):
23 | validator_param = {
24 | "name_in_english": dict(max_len=32, pattern='[a-zA-Z_]+'),
25 | "name_in_chinese": dict(max_len=32),
26 | "acl_name": dict(max_len=16, pattern='[a-zA-Z]+', allow_blank=True),
27 | "abbreviation": dict(max_len=8, pattern='[a-z]+'),
28 | "acl_file": dict(max_len=16, pattern='[a-zA-Z\.]+', allow_blank=True),
29 | }
30 |
31 | param = {}
32 | for k, v in validator_param.items():
33 | ok, format_str = valid_string(param_dict[k], **v)
34 | if not ok:
35 | raise BadParam(u'param validate error: %s, %s' % (k, v), msg_ch=u'%s: %s' % (k, format_str))
36 | if format_str:
37 | param[k] = format_str
38 | return param
39 |
40 |
41 | def add_isp_log(result, **kwargs):
42 | domain = result.pop('name_in_english')
43 | result.pop('username', None)
44 | return domain, {}, result
45 |
46 |
47 | def delete_isp_log(result, **kwargs):
48 | domain = kwargs['name_in_english']
49 | return domain, {}, {}
50 |
51 |
52 | def update_isp_log(result, **kwargs):
53 | domain = kwargs['name_in_english']
54 | return domain, {}, kwargs['update_data']
55 |
56 | def acl_migration_log(result, **kwargs):
57 | subnet = result.pop('subnet', kwargs['acl_subnet_id'])
58 | return subnet, {}, result
59 |
60 | def add_acl_subnet_log(result, subnet, acl, username):
61 | return acl, {}, {'subnet': subnet}
62 |
63 | def delete_acl_subnet_log(result, **kwargs):
64 | return result['origin_acl'] , {}, {'subnet': result['subnet']}
65 |
66 |
67 | @bp.route('/add/isp', methods=['POST'])
68 | @parse_params([], need_username=True)
69 | @resp_wrapper_json
70 | @add_web_opration_log('add_isp', get_op_info=add_isp_log)
71 | def add_isp(username):
72 | params = request.get_json(force=True)
73 | data = _validate_args(params)
74 | data['username'] = username
75 | ViewIspAclDal.add_isp(data)
76 | return data
77 |
78 |
79 | @bp.route('/delete/isp', methods=['POST'])
80 | @parse_params([dict(name='name_in_english', type=str, required=True, nullable=False)])
81 | @resp_wrapper_json
82 | @add_web_opration_log('delete_isp', get_op_info=delete_isp_log)
83 | def delete_isp(name_in_english):
84 | count = ViewIspAclDal.delete_isp(name_in_english)
85 | if count == 0:
86 | raise BadParam('No such isp: %s' % name_in_english, msg_ch=u'没有对应的运营商记录')
87 |
88 |
89 | @bp.route('/update/isp', methods=['POST'])
90 | @parse_params([dict(name='name_in_english', type=str, required=True, nullable=False),
91 | dict(name='update_data', type=dict, required=True, nullable=False)], need_username=True)
92 | @resp_wrapper_json
93 | @add_web_opration_log('update_isp', get_op_info=update_isp_log)
94 | def update_isp(name_in_english, update_data, username):
95 | count = ViewIspAclDal.update_isp(name_in_english, update_data, username)
96 | if count == 0:
97 | raise BadParam('No such isp: %s' % name_in_english, msg_ch=u'没有对应的运营商记录')
98 |
99 | @bp.route('/migrate_subnet_acl', methods=['POST'])
100 | @parse_params([dict(name='acl_subnet_id', type=int, required=True, nullable=False),
101 | dict(name='to_acl', type=str, required=True, nullable=False)])
102 | @resp_wrapper_json
103 | @add_web_opration_log('acl_migration', get_op_info=acl_migration_log)
104 | def migrate_subnet_acl(acl_subnet_id, to_acl):
105 | return ViewIspAclDal.migrate_acl(acl_subnet_id, to_acl)
106 |
107 | @bp.route('/add/acl_subnet', methods=['POST'])
108 | @parse_params([dict(name='subnet', type=str, required=True, nullable=False),
109 | dict(name='acl', type=str, required=True, nullable=False)], need_username=True)
110 | @resp_wrapper_json
111 | @add_web_opration_log('add_acl_subnet', get_op_info=add_acl_subnet_log)
112 | def add_acl_subnet(subnet, acl, username):
113 | return ViewIspAclDal.add_acl_subnet(subnet, acl, username)
114 |
115 | @bp.route('/delete/acl_subnet', methods=['POST'])
116 | @parse_params([dict(name='subnet_id', type=int, required=True, nullable=False)], need_username=True)
117 | @resp_wrapper_json
118 | @add_web_opration_log('delete_acl_subnet', get_op_info=delete_acl_subnet_log)
119 | def delete_acl_subnet(subnet_id, username):
120 | return ViewIspAclDal.delete_acl_subnet(subnet_id, username)
121 |
122 |
123 | @bp.route('/list/isp', methods=['GET'])
124 | @resp_wrapper_json
125 | def list_isp():
126 | return ViewIspAclDal.list_isp()
127 |
128 | @bp.route('/list/acl_subnet_by_ip', methods=['GET'])
129 | @parse_params([dict(name='ip', type=str, required=True, nullable=False)])
130 | @resp_wrapper_json
131 | def list_acl_subnet_by_ip(ip):
132 | return ViewIspAclDal.list_acl_subnet_by_ip(ip)
133 |
134 | @bp.route('/list/acl_isp_info', methods=['GET'])
135 | @resp_wrapper_json
136 | def list_acl_isp_info():
137 | return ViewIspAclDal.list_acl_isp()
138 |
139 |
140 | @bp.route('/list/migrate_subnet', methods=['GET'])
141 | @resp_wrapper_json
142 | def list_migrate_subnet():
143 | return ViewIspAclDal.get_migrate_subnet()
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/dnsdb_common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_common/__init__.py
--------------------------------------------------------------------------------
/dnsdb_common/dal/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from functools import wraps
4 |
5 | from flask_sqlalchemy import SQLAlchemy
6 |
7 | db = SQLAlchemy(session_options={
8 | 'autocommit': True
9 | })
10 |
11 |
12 | def commit_on_success(func):
13 | @wraps(func)
14 | def decorator(*kargs, **kwargs):
15 | with db.session.begin(subtransactions=True):
16 | return func(*kargs, **kwargs)
17 |
18 | return decorator
19 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from datetime import date
4 | from datetime import datetime
5 |
6 | from dnsdb_common.library.exception import BadParam
7 |
8 | from .. import db
9 |
10 | db.Model = db.Model
11 |
12 | __all__ = [
13 | "AnonymousUser",
14 | "AuditTimeMixin",
15 | "DeployHistory",
16 | "DnsHeader",
17 | "DnsHost",
18 | "DnsHostGroup",
19 | "OperationLog",
20 | "OperationLogDetail",
21 | "DnsNamedConf",
22 | "DnsRecord",
23 | "DnsSerial",
24 | "DnsZoneConf",
25 | "IpPool",
26 | "DnsColo",
27 | "Role",
28 | "Subnets",
29 | "User",
30 | "ViewAclCityCode",
31 | "ViewAclMigrateHistory",
32 | "ViewAclSubnet",
33 | "ViewConfigs",
34 | "ViewDomainNameState",
35 | "ViewDomainNames",
36 | "ViewIspStatus",
37 | "ViewIsps",
38 | "ViewMigrateDetail",
39 | "ViewMigrateHistory",
40 | "ViewRecords",
41 | "ViewSwitchIpDetail",
42 | "ViewSwitchIpHistory"
43 | ]
44 |
45 |
46 | class JsonMixin(object):
47 | def json_serialize(self, include=None, exclude=None):
48 | def __format(val):
49 | if isinstance(val, datetime):
50 | return val.strftime('%Y-%m-%d %H:%M:%S')
51 | elif isinstance(val, date):
52 | return val.strftime('%Y-%m-%d')
53 | return val
54 |
55 | filter_func = None
56 | if include is not None:
57 | if not isinstance(include, (list, tuple)):
58 | raise BadParam('param should be [list, tuple]')
59 | filter_func = lambda x: x in include
60 | elif exclude is not None:
61 | if not isinstance(exclude, (list, tuple)):
62 | raise BadParam('param should be [list, tuple]')
63 | filter_func = lambda x: x not in include
64 |
65 | fields = self.__mapper__.mapped_table.columns.keys()
66 | if filter_func is not None:
67 | fields = filter(filter_func, fields)
68 | return dict(((f, __format(getattr(self, f))) for f in fields))
69 |
70 |
71 | class AuditTimeMixin(object):
72 | created_time = db.Column(db.DateTime, default=datetime.now)
73 | updated_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
74 |
75 |
76 | from .deploy_history import DeployHistory
77 | from .dns_header import DnsHeader
78 | from .dns_host import DnsHost
79 | from .dns_host_group import DnsHostGroup
80 | from .operation_log import OperationLog
81 | from .operation_log_detail import OperationLogDetail
82 | from .dns_named_conf import DnsNamedConf
83 | from .dns_record import DnsRecord
84 | from .dns_serial import DnsSerial
85 | from .dns_zone_conf import DnsZoneConf
86 | from .ippool import IpPool
87 | from .dns_colos import DnsColo
88 | from .subnets import Subnets
89 | from .user import AnonymousUser
90 | from .user import Role
91 | from .user import User
92 | from .view_acl_city_code import ViewAclCityCode
93 | from .view_acl_migrate_history import ViewAclMigrateHistory
94 | from .view_acl_subnets import ViewAclSubnet
95 | from .view_config import ViewConfigs
96 | from .view_domain_name_state import ViewDomainNameState
97 | from .view_domain_names import ViewDomainNames
98 | from .view_isp_status import ViewIspStatus
99 | from .view_isps import ViewIsps
100 | from .view_migrate_detail import ViewMigrateDetail
101 | from .view_migrate_history import ViewMigrateHistory
102 | from .view_records import ViewRecords
103 | from .view_switch_ip_detail import ViewSwitchIpDetail
104 | from .view_switch_ip_history import ViewSwitchIpHistory
105 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/deploy_history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class DeployHistory(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_deploy_history'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | rtx_id = db.Column(db.String(50), nullable=False)
12 | deploy_desc = db.Column(db.Text, nullable=False)
13 | state = db.Column(db.String(50), nullable=False)
14 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_colos.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class DnsColo(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_colo_config'
9 |
10 | id = db.Column(db.Integer)
11 | colo_name = db.Column(db.String(64), primary_key=True)
12 | colo_group = db.Column(db.String(64), primary_key=True)
13 | create_user = db.Column(db.String(64), nullable=False)
14 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_header.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class DnsHeader(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_dns_zone_header'
9 |
10 | zone_name = db.Column(db.String(50), primary_key=True)
11 | header_content = db.Column(db.Text, nullable=False)
12 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_host.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class DnsHost(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_dns_host'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | host_name = db.Column(db.String(100), unique=True, nullable=False)
12 | host_group = db.Column(db.String(32), nullable=False)
13 | host_ip = db.Column(db.String(32), nullable=False)
14 | host_conf_md5 = db.Column(db.String(100))
15 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_host_group.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class DnsHostGroup(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_dns_host_group'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | group_name = db.Column(db.String(32), unique=True, nullable=False)
12 | group_type = db.Column(db.String(32), nullable=False)
13 | # 组配置md5 和线上配置定时对比
14 | group_conf_md5 = db.Column(db.String(100))
15 | reload_status = db.Column(db.Boolean, default=True)
16 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_named_conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | # named conf for host group
8 | class DnsNamedConf(db.Model, AuditTimeMixin):
9 | __tablename__ = 'tb_dns_named_conf'
10 |
11 | name = db.Column(db.String(64), primary_key=True)
12 | conf_content = db.Column(db.Text, nullable=False)
13 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_record.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class DnsRecord(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_dns_record'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | domain_name = db.Column(db.String(512), nullable=False)
12 | record = db.Column(db.String(512), nullable=False)
13 | zone_name = db.Column(db.String(20), nullable=False)
14 | update_user = db.Column(db.String(50), nullable=False)
15 | record_type = db.Column(db.String(20), nullable=False)
16 | ttl = db.Column(db.Integer, nullable=False, default=0)
17 | onoff = db.Column(db.Boolean, nullable=False, default=True)
18 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_serial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class DnsSerial(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_dns_zone_serial'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | zone_name = db.Column(db.String(50), unique=True, nullable=False)
12 | zone_group = db.Column(db.String(64), nullable=False)
13 | serial_num = db.Column(db.BigInteger, nullable=False)
14 | update_serial_num = db.Column(db.BigInteger, nullable=False)
15 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/dns_zone_conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class DnsZoneConf(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_dns_named_zone'
9 |
10 | zone_name = db.Column(db.String(50), primary_key=True)
11 | # zone在不同group上的配置
12 | zone_conf = db.Column(db.Text, nullable=False)
13 | zone_group = db.Column(db.String(64), primary_key=True)
14 | # 0 反解
15 | # 1 机房
16 | # 2 普通
17 | zone_type = db.Column(db.Integer, nullable=False)
18 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/ippool.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class IpPool(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_ippool'
9 |
10 | id = db.Column(db.Integer)
11 | fixed_ip = db.Column(db.String(256), primary_key=True)
12 | region = db.Column(db.String(50), nullable=False)
13 | allocated = db.Column(db.Boolean, nullable=False, default=True)
14 | is_ipv6 = db.Column(db.Boolean, nullable=False, default=False)
15 |
16 | def __repr__(self):
17 | return ' [fixed_ip: %s, region: %s]' % (self.ip, self.region)
18 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/operation_log.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .. import db
4 | from . import JsonMixin
5 |
6 |
7 | class OperationLog(db.Model, JsonMixin):
8 | __tablename__ = 'tb_operation_log'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | rtx_id = db.Column(db.String(256), nullable=False)
12 | op_domain = db.Column(db.String(256), nullable=False)
13 | op_type = db.Column(db.String(32), nullable=False)
14 | op_before = db.Column(db.String(2048), nullable=False)
15 | op_after = db.Column(db.String(2048), nullable=False)
16 | op_time = db.Column(db.DateTime, nullable=False)
17 | op_result = db.Column(db.String(32), nullable=False)
18 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/operation_log_detail.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .. import db
4 |
5 |
6 | class OperationLogDetail(db.Model):
7 | __tablename__ = 'tb_operation_log_detail'
8 |
9 | id = db.Column(db.Integer, primary_key=True)
10 | log_id = db.Column(db.Integer, nullable=False)
11 | detail = db.Column(db.String(1024), default='')
12 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/operation_type.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class OperationType(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_operation_type'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | op_type = db.Column(db.String(64), unique=True, nullable=False)
12 | op_chinese = db.Column(db.String(128), unique=True, nullable=False)
13 | # logs = db.relationship('User', backref='role', lazy='dynamic')
14 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/subnets.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class Subnets(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_subnets'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | region_name = db.Column(db.String(80), nullable=False, unique=True)
12 | subnet = db.Column(db.String(50), nullable=False)
13 | create_user = db.Column(db.String(50), nullable=False)
14 | comment = db.Column(db.String(100))
15 | colo = db.Column(db.String(64), nullable=False, default='')
16 | intranet = db.Column(db.Boolean, nullable=False, default=False)
17 | is_ipv6 = db.Column(db.Boolean, nullable=False, default=False)
18 |
19 | def __repr__(self):
20 | return ' [region_name: %s, subnet: %s]' % (self.region_name, self.subnet)
21 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/user.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from datetime import datetime
4 |
5 | from flask import current_app
6 | from flask_login import UserMixin, AnonymousUserMixin
7 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
8 | from werkzeug.security import generate_password_hash, check_password_hash
9 |
10 | from . import AuditTimeMixin, JsonMixin
11 | from .. import db
12 |
13 |
14 | class Permission(object):
15 | GET = 1
16 | POST = 2
17 | ADMIN = 4
18 |
19 |
20 | class Role(db.Model, JsonMixin):
21 | __tablename__ = 'roles'
22 | id = db.Column(db.Integer, primary_key=True)
23 | name = db.Column(db.String(64), unique=True)
24 | default = db.Column(db.Boolean, default=False, index=True)
25 | permissions = db.Column(db.Integer)
26 | name_ch = db.Column(db.String(64), unique=True)
27 | users = db.relationship('User', backref='role', lazy='dynamic')
28 |
29 | def __init__(self, **kwargs):
30 | super(Role, self).__init__(**kwargs)
31 | if self.permissions is None:
32 | self.permissions = 0
33 |
34 | @staticmethod
35 | def insert_roles():
36 | roles = {
37 | # 'User': ([Permission.GET], u'普通用户'),
38 | 'Operator': ([Permission.GET, Permission.POST], u'管理员'),
39 | 'Administrator': ([Permission.GET, Permission.POST, Permission.ADMIN], u'超级管理员')
40 | }
41 | default_role = 'Admin'
42 | for r in roles:
43 | role = Role.query.filter_by(name=r).first()
44 | if role is None:
45 | role = Role(name=r)
46 | role.reset_permissions()
47 | for perm in roles[r][0]:
48 | role.add_permission(perm)
49 | role.default = (role.name == default_role)
50 | role.name_ch = roles[r][1]
51 | db.session.add(role)
52 | db.session.commit()
53 |
54 | def add_permission(self, perm):
55 | if not self.has_permission(perm):
56 | self.permissions += perm
57 |
58 | def remove_permission(self, perm):
59 | if self.has_permission(perm):
60 | self.permissions -= perm
61 |
62 | def reset_permissions(self):
63 | self.permissions = 0
64 |
65 | def has_permission(self, perm):
66 | return self.permissions & perm == perm
67 |
68 | def __repr__(self):
69 | return '' % self.name
70 |
71 |
72 | class User(db.Model, UserMixin, AuditTimeMixin, JsonMixin):
73 | __tablename__ = 'users'
74 | id = db.Column(db.Integer, primary_key=True)
75 | username = db.Column(db.String(64), unique=True, index=True)
76 | email = db.Column(db.String(64), unique=True, index=True)
77 | role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
78 | password_hash = db.Column(db.String(128))
79 |
80 | def __init__(self, **kwargs):
81 | super(User, self).__init__(**kwargs)
82 | self.role = Role.query.get(self.role_id)
83 | if self.role is None:
84 | self.role = Role.query.filter_by(default=True).first()
85 | self.role_id = self.role.id
86 |
87 | def json_serialize(self, include=('username', 'email', 'role_id'), exclude=None):
88 | data = super(User, self).json_serialize(include=include, exclude=exclude)
89 | if 'role' in include:
90 | data['role'] = self.role.name
91 | return data
92 |
93 | @property
94 | def password(self):
95 | raise AttributeError('password is not a readable attribute')
96 |
97 | @password.setter
98 | def password(self, password):
99 | self.password_hash = generate_password_hash(password)
100 |
101 | def verify_password(self, password):
102 | return check_password_hash(self.password_hash, password)
103 |
104 | def generate_confirmation_token(self, expiration=3600):
105 | s = Serializer(current_app.config['SECRET_KEY'], expiration)
106 | return s.dumps({'confirm': self.id}).decode('utf-8')
107 |
108 | def can(self, perm):
109 | return self.role is not None and self.role.has_permission(perm)
110 |
111 | def is_user(self):
112 | return self.can(Permission.GET)
113 |
114 | def is_administrator(self):
115 | return self.can(Permission.ADMIN)
116 |
117 | def ping(self):
118 | self.update_time = datetime.utcnow()
119 | db.session.add(self)
120 |
121 | def generate_auth_token(self, expiration):
122 | s = Serializer(current_app.config['SECRET_KEY'],
123 | expires_in=expiration)
124 | return s.dumps({'id': self.id}).decode('utf-8')
125 |
126 | def __repr__(self):
127 | return '' % self.username
128 |
129 |
130 | class AnonymousUser(AnonymousUserMixin):
131 | def can(self, permissions):
132 | return False
133 |
134 | def is_administrator(self):
135 | return False
136 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_acl_city_code.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewAclCityCode(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_acl_city_code'
9 | code = db.Column(db.String(64), primary_key=True)
10 | country = db.Column(db.String(64), nullable=False)
11 | province = db.Column(db.String(64), nullable=False)
12 | city = db.Column(db.String(64))
13 |
14 | def __repr__(self):
15 | return 'ViewAclCityCode [code={}, province={}]'.format(
16 | self.code,
17 | self.province
18 | )
19 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_acl_migrate_history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from sqlalchemy.orm import relationship
4 |
5 | from .. import db
6 |
7 |
8 | class ViewAclMigrateHistory(db.Model):
9 | __tablename__ = 'tb_view_acl_migrate_history'
10 | id = db.Column(db.Integer, primary_key=True)
11 | subnet_id = db.Column(db.Integer, db.ForeignKey('tb_view_acl_subnets.id'))
12 | from_acl = db.Column(db.String(32))
13 | to_acl = db.Column(db.String(32), nullable=False)
14 | origin_acl = db.Column(db.String(32), nullable=False)
15 | create_user = db.Column(db.String(64), nullable=False)
16 | create_time = db.Column(db.DateTime, nullable=False)
17 | # 'migrated', 'recovered', 'remigrated'
18 | status = db.Column(db.String(32), default='migrated')
19 | reloaded = db.Column(db.Boolean, nullable=False, default=False)
20 | subnet = relationship("ViewAclSubnet")
21 |
22 | def __str__(self):
23 | return 'ViewAclSubnet[subnet=%s, from_isp=%s, to_isp=%s]' % (
24 | self.subnet_id,
25 | self.from_view,
26 | self.to_view
27 | )
28 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_acl_subnets.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class ViewAclSubnet(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_view_acl_subnets'
9 | id = db.Column(db.Integer, primary_key=True)
10 | subnet = db.Column(db.String(32), nullable=False, unique=True)
11 | start_ip = db.Column(db.Float, nullable=False)
12 | end_ip = db.Column(db.Float, nullable=False)
13 | origin_acl = db.Column(db.String(32), nullable=False)
14 | now_acl = db.Column(db.String(32), nullable=False)
15 | update_user = db.Column(db.String(64), nullable=False)
16 | is_ipv6 = db.Column(db.Boolean, nullable=False, default=False)
17 |
18 | def __str__(self):
19 | return 'ViewAclSubnet[subnet=%s, now_acl=%s, start=%s, end=%s]' % (
20 | self.subnet,
21 | self.now_acl,
22 | self.start,
23 | self.end
24 | )
25 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_config.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewConfigs(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_configs'
9 |
10 | key = db.Column(db.String(256), nullable=False, primary_key=True)
11 | value = db.Column(db.String(256), default='')
12 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_domain_name_state.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewDomainNameState(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_domain_name_state'
9 |
10 | domain_name = db.Column(db.String(256), nullable=False, primary_key=True)
11 | origin_enabled_rooms = db.Column(db.String(256), default="[]", nullable=False)
12 | origin_state = db.Column(db.String(32), default='disabled', nullable=False)
13 | enabled_rooms = db.Column(db.String(256), default="[]", nullable=False)
14 | isp = db.Column(db.String(256), primary_key=True)
15 | state = db.Column(db.String(32), default='disabled')
16 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_domain_names.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewDomainNames(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_domain_name'
9 |
10 | domain_name = db.Column(db.String(256), primary_key=True)
11 | cname = db.Column(db.String(256), nullable=False)
12 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_isp_status.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewIspStatus(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_isps_status'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | history_id = db.Column(db.Integer)
12 | recover_id = db.Column(db.Integer)
13 | room = db.Column(db.String(64), nullable=False)
14 | # chinanet, cmnet, unicom
15 | isp = db.Column(db.String(64), nullable=False)
16 | is_health = db.Column(db.Boolean, default=False, nullable=False)
17 | closed = db.Column(db.Boolean, default=False, nullable=False)
18 | update_user = db.Column(db.String(64), nullable=False)
19 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_isps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | class ViewIsps(db.Model, AuditTimeMixin, JsonMixin):
8 | __tablename__ = 'tb_view_isps'
9 |
10 | name_in_english = db.Column(db.String(64), primary_key=True)
11 | abbreviation = db.Column(db.String(32), unique=True, nullable=False)
12 | name_in_chinese = db.Column(db.String(64), unique=True, nullable=False)
13 | acl_name = db.Column(db.String(64), unique=True)
14 | acl_file = db.Column(db.String(64))
15 | username = db.Column(db.String(64), nullable=False)
16 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_migrate_detail.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | from .. import db
6 |
7 |
8 | # 记录每次迁移的详情
9 | class ViewMigrateDetail(db.Model):
10 | __tablename__ = 'tb_view_migrate_detail'
11 | id = db.Column(db.Integer, primary_key=True)
12 | migrate_id = db.Column(db.Integer, nullable=False)
13 | domain_name = db.Column(db.String(256), nullable=False)
14 | before_enabled_server_rooms = db.Column(db.String(256), default='[]')
15 | after_enabled_server_rooms = db.Column(db.String(256), default='[]')
16 | isp = db.Column(db.String(256))
17 | before_state = db.Column(db.String(32), default='disabled')
18 | after_state = db.Column(db.String(32), default='disabled')
19 |
20 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_migrate_history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin, JsonMixin
4 | from .. import db
5 |
6 |
7 | # 记录迁移进度
8 | class ViewMigrateHistory(db.Model, AuditTimeMixin, JsonMixin):
9 | __tablename__ = 'tb_view_migrate_history'
10 |
11 | id = db.Column(db.Integer, primary_key=True)
12 | migrate_rooms = db.Column(db.String(256), nullable=False)
13 | migrate_isps = db.Column(db.String(256), nullable=False)
14 | dst_rooms = db.Column(db.String(256), nullable=False)
15 | migrate_info = db.Column(db.Text)
16 | # 迁移规则: 'migrating', 'migrated', 'error', 'recovered'
17 | state = db.Column(db.String(32), default='migrating')
18 | cur = db.Column(db.Integer, nullable=False)
19 | all = db.Column(db.Integer, nullable=False)
20 | rtx_id = db.Column(db.String(256), nullable=False)
21 |
22 | # 不能并发的状态
23 | check_states = ('recovering', 'migrating')
24 |
25 | def __init__(self, migrate_rooms, migrate_isps, dst_rooms, state, cur, all, rtx_id):
26 | self.migrate_rooms = migrate_rooms
27 | self.migrate_isps = migrate_isps
28 | self.dst_rooms = dst_rooms
29 | self.state = state
30 | self.cur = cur
31 | self.all = all
32 | self.rtx_id = rtx_id
33 |
34 | def update(self, migrate_rooms, migrate_isps, dst_rooms, state, cur, all, rtx_id):
35 | self.migrate_rooms = migrate_rooms
36 | self.migrate_isps = migrate_isps
37 | self.dst_rooms = dst_rooms
38 | self.state = state
39 | self.cur = cur
40 | self.all = all
41 | self.rtx_id = rtx_id
42 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_records.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | class ViewRecords(db.Model, AuditTimeMixin):
8 | __tablename__ = 'tb_view_record'
9 |
10 | id = db.Column(db.Integer, primary_key=True)
11 | domain_name = db.Column(db.String(256), nullable=False)
12 | record = db.Column(db.String(256), nullable=False)
13 | record_type = db.Column(db.String(32), nullable=False)
14 | ttl = db.Column(db.Integer, nullable=False, default=60)
15 | property = db.Column(db.String(256), default='none')
16 | zone_name = db.Column(db.String(50), nullable=False)
17 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_switch_ip_detail.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | from .. import db
6 |
7 |
8 | class ViewSwitchIpDetail(db.Model):
9 | __tablename__ = 'tb_view_switch_ip_detail'
10 | id = db.Column(db.Integer, primary_key=True)
11 | switch_id = db.Column(db.Integer, nullable=False)
12 | domain_name = db.Column(db.String(256), nullable=False, primary_key=True)
13 | before_enabled_server_rooms = db.Column(db.String(256), default='[]')
14 | isp = db.Column(db.String(256), primary_key=True)
15 | before_state = db.Column(db.String(32), default='disabled')
16 | after_state = db.Column(db.String(32), default='disabled')
17 |
18 | def __init__(self, switch_id, domain_name, before_enabled_server_rooms, isp, before_state, after_state):
19 | self.switch_id = switch_id
20 | self.domain_name = domain_name
21 | self.before_enabled_server_rooms = json.dumps(before_enabled_server_rooms)
22 | self.isp = isp
23 | self.before_state = before_state
24 | self.after_state = after_state
25 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/models/view_switch_ip_history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import AuditTimeMixin
4 | from .. import db
5 |
6 |
7 | # 记录ip切换记录
8 | class ViewSwitchIpHistory(db.Model, AuditTimeMixin):
9 | __tablename__ = 'tb_view_switch_ip_history'
10 | id = db.Column(db.Integer, primary_key=True)
11 | switch_ip = db.Column(db.String(32), nullable=False)
12 | switch_type = db.Column(db.String(32), nullable=False)
13 | switch_to = db.Column(db.String(32), nullable=False)
14 | state = db.Column(db.String(32), default='switched')
15 | rtx_id = db.Column(db.String(256), nullable=False)
16 |
17 | def __init__(self, switch_ip, switch_type, switch_to, state, update_at, rtx_id):
18 | self.switch_ip = switch_ip
19 | self.switch_type = switch_type
20 | self.switch_to = switch_to
21 | self.state = state
22 | self.update_at = update_at
23 | self.rtx_id = rtx_id
24 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/operation_log.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 | from datetime import datetime
5 | from datetime import timedelta
6 |
7 | from . import db, commit_on_success
8 | from .models import OperationLog
9 | from .models import OperationLogDetail
10 |
11 |
12 | def format_time(time_str, time_type):
13 | if time_type == 'start':
14 | t = datetime.strptime(time_str, '%Y-%m-%d')
15 | else:
16 | t = datetime.strptime(time_str, '%Y-%m-%d')
17 | t = t + timedelta(days=1)
18 | return t.strftime('%Y-%m-%d %H:%M:%S')
19 |
20 |
21 | class OperationLogDal(object):
22 | @staticmethod
23 | def insert_operation_log_with_dict(rtx_id, op_domain, op_type, op_before, op_after, op_result, reason=None):
24 | session = db.session
25 | with session.begin(subtransactions=True):
26 | item = OperationLog(
27 | rtx_id=rtx_id,
28 | op_domain=op_domain,
29 | op_type=op_type,
30 | op_before=json.dumps(op_before),
31 | op_after=json.dumps(op_after),
32 | op_time=datetime.now(),
33 | op_result=op_result
34 | )
35 | session.add(item)
36 |
37 | if op_result == 'fail' and reason is not None:
38 | OperationLogDal.add_log_detail(OperationLog.id, reason)
39 | return item.id
40 |
41 | @staticmethod
42 | def add_log_detail(log_id, reason):
43 | session = db.session
44 | if not isinstance(reason, str):
45 | reason = str(reason)
46 | if len(reason) > 1024:
47 | reason = reason[:1024]
48 | with session.begin(subtransactions=True):
49 | session.add(OperationLogDetail(
50 | log_id=log_id,
51 | detail=reason
52 | ))
53 |
54 | @staticmethod
55 | def list_operation_log(condition):
56 | query = OperationLog.query
57 | if condition['start_time'] != '' and condition['start_time'] != '':
58 | query = query.filter(
59 | OperationLog.op_time.between(format_time(condition['start_time'], 'start'),
60 | format_time(condition['end_time'], 'end')))
61 | if 'domain' in condition:
62 | query = query.filter(OperationLog.op_domain == condition['domain'])
63 | if 'type' in condition:
64 | query = query.filter(OperationLog.op_type == condition['type'])
65 | if 'rtx_id' in condition:
66 | query = query.filter(OperationLog.rtx_id == condition['rtx_id'])
67 | total = query.count()
68 | logs = query.order_by(OperationLog.op_time.desc()).offset(
69 | condition['page_size'] * (condition['page'] - 1)).limit(
70 | condition['page_size']).all()
71 | result = []
72 | for log in logs:
73 | result.append(log.json_serialize())
74 |
75 | return {'total': total, 'logs': result}
76 |
77 | @staticmethod
78 | def get_log_detail(log_id):
79 | item = OperationLogDetail.query.filter_by(log_id=log_id).first()
80 | if not item:
81 | return ''
82 | return item.detail
83 |
84 | @staticmethod
85 | def create_deploy_job(user, deploy_info, conf_type, unfinished):
86 | return OperationLogDal.insert_operation_log_with_dict(user, conf_type,
87 | 'conf_deploy', deploy_info,
88 | dict(successed=[], failed={}, unfinished=unfinished), 'wait')
89 |
90 | @staticmethod
91 | def get_deploy_job(job_id):
92 | return OperationLog.query.get(job_id)
93 |
94 | @staticmethod
95 | def reset_deploy_job(user, job_id):
96 | item = OperationLog.query.get(job_id)
97 | if not item:
98 | return item
99 |
100 | op_info = json.loads(item.op_before)
101 | op_domain = item.op_domain
102 | unfinished = []
103 | if op_domain == 'named.conf':
104 | for group, info in op_info.items():
105 | unfinished.extend(info['hosts'])
106 | elif op_domain == 'acl':
107 | for group, hosts in op_info.get('hosts', {}).items():
108 | unfinished.extend(hosts)
109 | elif op_domain == 'zone':
110 | unfinished.extend(op_info.get('hosts', []))
111 |
112 | data = dict(op_time=datetime.now(), op_result='wait', op_after=json.dumps(dict(successed=[], failed={}, unfinished=unfinished)))
113 | return OperationLogDal.update_opration_log(job_id, data)
114 |
115 | @staticmethod
116 | @commit_on_success
117 | def update_opration_log(job_id, data):
118 | return OperationLog.query.filter_by(id=job_id).update(data)
119 |
120 | @staticmethod
121 | def update_deploy_info(deploy_id, host, is_success, msg):
122 | # json.dumps(dict(success=[], failed={}, unfinish=[]))
123 | job = OperationLogDal.get_deploy_job(deploy_id)
124 | op_after = json.loads(job.op_after)
125 | success = op_after.get('successed', [])
126 | unfinish = op_after.get('unfinished', [])
127 | failed = op_after.get('failed', {})
128 |
129 | if host in unfinish:
130 | unfinish.remove(host)
131 | op_after['unfinished'] = unfinish
132 |
133 | if is_success:
134 | success.append(host)
135 | op_after['successed'] = success
136 | else:
137 | failed[host] = msg
138 | op_after['failed'] = failed
139 |
140 | data = dict(op_after=json.dumps(op_after))
141 | if not unfinish:
142 | data['op_result'] = 'ok'
143 | if failed:
144 | data['op_result'] = 'fail'
145 | OperationLogDal.update_opration_log(deploy_id, data)
146 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/subnet_ip.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import ipaddress
3 |
4 | from dnsdb_common.library.exception import BadParam
5 | from dnsdb_common.library.utils import format_ip
6 | from . import commit_on_success
7 | from . import db
8 | from .models import DnsColo
9 | from .models import DnsRecord
10 | from .models import IpPool
11 | from .models import Subnets
12 |
13 |
14 | class SubnetIpDal(object):
15 | @staticmethod
16 | def get_colo_by_group(group):
17 | return [record.colo_name
18 | for record in
19 | db.session.query(DnsColo.colo_name).filter_by(colo_group=group).order_by(DnsColo.colo_name)]
20 |
21 | @staticmethod
22 | def list_region(**condition):
23 | q = Subnets.query
24 | if condition:
25 | q = q.filter_by(**condition)
26 | return [item.json_serialize() for item in q.order_by(Subnets.region_name, Subnets.subnet)]
27 |
28 | @staticmethod
29 | def get_region_by_ip(ip):
30 | ip, _ = format_ip(ip)
31 | record = IpPool.query.filter_by(fixed_ip=ip).first()
32 | if not record:
33 | raise BadParam('no such ip: %s' % ip, msg_ch=u'没有对应的ip记录')
34 | return SubnetIpDal.get_region_by_name(record.region)
35 |
36 | @staticmethod
37 | def get_region_by_name(region):
38 | record = Subnets.query.filter_by(region_name=region).first()
39 | if not record:
40 | raise BadParam('no such subnet with region_name: %s' % region, msg_ch=u'没有对应的网段记录')
41 | return record.json_serialize()
42 |
43 | @staticmethod
44 | def get_region_by_name_like(region):
45 | region = '%{}%'.format(region)
46 | records = Subnets.query.filter(Subnets.region_name.like(region))
47 | return [record.json_serialize() for record in records]
48 |
49 | @staticmethod
50 | def is_intranet_region(region):
51 | record = Subnets.query.filter_by(region_name=region).first()
52 | if not record:
53 | raise BadParam('no such subnet with region_name: %s' % region, msg_ch=u'没有对应的网段记录')
54 | return record.intranet
55 |
56 | @staticmethod
57 | def is_ip_exist(record):
58 | return IpPool.query.filter_by(fixed_ip=record).first() is not None
59 |
60 | @staticmethod
61 | def get_subnet_ip(region):
62 | records = IpPool.query.outerjoin(DnsRecord, DnsRecord.record == IpPool.fixed_ip).add_columns(
63 | IpPool.fixed_ip, IpPool.allocated,
64 | DnsRecord.domain_name).filter(IpPool.region == region).order_by(IpPool.fixed_ip)
65 | result = [{"ip": item.fixed_ip, "domain": item.domain_name} for item in records]
66 | return result
67 |
68 | @staticmethod
69 | def add_subnet(subnet, region, colo, comment, username):
70 | subnet = ipaddress.ip_network(subnet)
71 | intranet = subnet.is_private
72 | net_id = subnet.network_address
73 | broadcast_ip = subnet.broadcast_address
74 | is_ipv6 = (subnet.version == 6)
75 | ips_dict_list = []
76 | for i in subnet:
77 | if i == net_id or i == broadcast_ip:
78 | continue
79 | ips_dict_list.append({
80 | 'region': region,
81 | 'fixed_ip': str(i),
82 | 'is_ipv6': is_ipv6
83 | })
84 | if Subnets.query.filter_by(region_name=region).first():
85 | raise BadParam('region already exist', msg_ch='网段名已存在')
86 | try:
87 | with db.session.begin(subtransactions=True):
88 | subnet_item = Subnets(
89 | region_name=region,
90 | subnet=str(subnet),
91 | create_user=username,
92 | intranet=intranet,
93 | colo=colo,
94 | is_ipv6=is_ipv6
95 | )
96 | if comment:
97 | subnet_item.comment = comment
98 | db.session.add(subnet_item)
99 | db.session.bulk_insert_mappings(IpPool, ips_dict_list)
100 | except Exception:
101 | raise BadParam('Ip conflict with other regions', msg_ch=u'和已有的网段有交叉,请检查后重试')
102 |
103 | @staticmethod
104 | @commit_on_success
105 | def delete_subnet(subnet, region):
106 | record = Subnets.query.filter_by(region_name=region, subnet=subnet).first()
107 | if not record:
108 | raise BadParam('Region does not exist: %s' % region, msg_ch=u'网段不存在')
109 | # 删除一个region
110 | ip_records = SubnetIpDal.get_subnet_ip(region)
111 | if list(filter(lambda x: x['domain'], ip_records)):
112 | raise BadParam('Region %s has records,delete failed!' % region, msg_ch=u'网段正在使用中,不允许删除')
113 |
114 | Subnets.query.filter_by(region_name=region, subnet=subnet).delete()
115 | IpPool.query.filter_by(region=region).delete()
116 |
117 |
118 | @staticmethod
119 | @commit_on_success
120 | def rename_subnet(old_region, new_region, username):
121 | if Subnets.query.filter_by(region_name=new_region).first():
122 | raise BadParam("Region %s existed, rename %s failed" % (new_region, old_region),
123 | msg_ch=u'%s已经存在' % new_region)
124 | if not Subnets.query.filter_by(region_name=old_region).first():
125 | raise BadParam("Region %s does not existed, rename failed" % old_region,
126 | msg_ch=u'%s不存在' % old_region)
127 | Subnets.query.filter(Subnets.region_name == old_region).update({
128 | "region_name": new_region
129 | })
130 | IpPool.query.filter(IpPool.region == old_region).update({
131 | 'region': new_region
132 | })
133 |
134 | @staticmethod
135 | def get_subnets_by_condition(**kwargs):
136 | session = db.session
137 | query = session.query(Subnets)
138 | if kwargs:
139 | query = query.filter_by(**kwargs)
140 | return query.order_by(Subnets.region_name, Subnets.subnet).all()
141 |
142 | @staticmethod
143 | def bulk_update_subnet(update_mapping):
144 | session = db.session
145 | with session.begin(subtransactions=True):
146 | session.bulk_update_mappings(Subnets, update_mapping)
147 |
--------------------------------------------------------------------------------
/dnsdb_common/dal/user.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import commit_on_success, db
4 | from .models import User, Role
5 | from ..library.exception import BadParam
6 |
7 |
8 | class UserDal(object):
9 | ALLOWED_ROLES = [u'owner', u'dev']
10 |
11 | def __init__(self):
12 | pass
13 |
14 | @staticmethod
15 | def get_roles():
16 | return {item.id: item.name_ch for item in Role.query.all()}
17 |
18 | @staticmethod
19 | def get_role_name(role_id):
20 | role = Role.query.get(role_id)
21 | if role:
22 | return role.name
23 | else:
24 | return None
25 |
26 | @staticmethod
27 | def get_user_info(**kwargs):
28 | return User.query.filter_by(**kwargs).first()
29 |
30 | @staticmethod
31 | @commit_on_success
32 | def add_user(username, email, password, role_id):
33 | if User.query.filter_by(username=username).first():
34 | raise BadParam('user with username %s already exist.' % username)
35 | if User.query.filter_by(email=email).first():
36 | raise BadParam('user with email %s already exist.' % email)
37 | user = User(username=username, email=email, password=password, role_id=role_id)
38 | db.session.add(user)
39 |
40 | @staticmethod
41 | def list_user(role_id='', page=1, page_size=10):
42 | query = User.query
43 | if role_id:
44 | query = query.filter_by(role_id=role_id)
45 | # if page <= 0:
46 | # page = 1
47 | return [obj.json_serialize() for obj in (query.
48 | offset((page - 1) * page_size).
49 | limit(page_size).all())]
50 |
51 | # @staticmethod
52 | # def reset_password(token, new_password):
53 | # s = Serializer(current_app.config['SECRET_KEY'])
54 | # try:
55 | # data = s.loads(token.encode('utf-8'))
56 | # except:
57 | # return False
58 | # user = User.query.get(data.get('reset'))
59 | # if user is None:
60 | # return False
61 | # user.password = new_password
62 | # db.session.add(user)
63 | # return True
64 |
65 | @staticmethod
66 | @commit_on_success
67 | def delete_user(username):
68 | User.query.filter_by(username=username).delete()
69 |
--------------------------------------------------------------------------------
/dnsdb_common/library/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_common/library/__init__.py
--------------------------------------------------------------------------------
/dnsdb_common/library/api.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 |
5 | import requests
6 | from oslo_config import cfg
7 | from requests.auth import HTTPBasicAuth
8 |
9 | from .exception import DnsdbException
10 |
11 | CONF = cfg.CONF
12 |
13 |
14 | class Api(object):
15 | def __init__(self, root, username='', password='', headers=None,
16 | resp_wrapper=lambda x, y: x):
17 | self.root = root
18 | self.auth = HTTPBasicAuth(username, password)
19 | self.content_type_form = {
20 | 'Content-type': 'application/x-www-form-urlencoded'}
21 | self.content_type_json = {
22 | 'Content-type': 'application/json'}
23 | self.headers = headers or {}
24 | self.resp_wrapper = resp_wrapper
25 |
26 | def _wrap_headers(self, headers, ctype='form'):
27 | _headers = {}
28 | _headers.update(self.headers)
29 | _headers.update(headers or {})
30 | content_type = 'application/x-www-form-urlencoded' if ctype == 'form' \
31 | else 'application/json'
32 | _headers.update({
33 | 'Content-type': content_type
34 | })
35 | return _headers
36 |
37 | def _get(self, url, params=None, headers=None, ctype='form', **kwargs):
38 | url = '%s%s' % (self.root, url)
39 | headers = self._wrap_headers(headers, ctype=ctype)
40 | params = params if params else {}
41 | resp = requests.get(
42 | url, auth=self.auth, headers=headers, params=params, **kwargs)
43 | req = dict(url=url, headers=headers, params=params)
44 | req.update(kwargs)
45 | return self.resp_wrapper(resp, req)
46 |
47 | def _post(self, url, headers=None, data=None, params=None,
48 | ctype='form', **kwargs):
49 | url = '%s%s' % (self.root, url)
50 | headers = self._wrap_headers(headers, ctype=ctype)
51 | params = params if params else {}
52 | data = data if data else {}
53 | if ctype == 'json':
54 | data = json.dumps(data)
55 | resp = requests.post(
56 | url, auth=self.auth, headers=headers, params=params, data=data,
57 | verify=False, **kwargs)
58 | req = dict(
59 | url=url, headers=headers, params=params, data=data, verify=False)
60 | req.update(kwargs)
61 | return self.resp_wrapper(resp, req)
62 |
63 | def get_form(self, url, *args, **kwargs):
64 | return self._get(url, *args, **dict(kwargs, **{'ctype': 'form'}))
65 |
66 | def get_json(self, url, *args, **kwargs):
67 | return self._get(url, *args, **dict(kwargs, **{'ctype': 'json'}))
68 |
69 | def post_form(self, url, *args, **kwargs):
70 | return self._post(url, *args, **dict(kwargs, **{'ctype': 'form'}))
71 |
72 | def post_json(self, url, *args, **kwargs):
73 | return self._post(url, *args, **dict(kwargs, **{'ctype': 'json'}))
74 |
75 |
76 | def _updater_resp_wrapper(resp, req):
77 | try:
78 | resp = resp.json()
79 | except Exception as ex:
80 | raise DnsdbException(
81 | u'DnsdbApi request error', 500, detail=dict(
82 | request=req, ex=str(ex), reason=resp.reason,
83 | status=resp.status_code),
84 | msg_ch=u'DnsUpdater调用失败')
85 | if int(resp.get('status', 200)) != 200 or resp.get('errcode', 0) != 0:
86 | raise DnsdbException(u'DnsUpdater调用失败', 400, json.dumps(resp))
87 | return resp
88 |
89 |
90 | class DnsUpdaterApi(Api):
91 | def __init__(self, host_ip, username='', password='', headers=None):
92 | port = CONF.api.dnsupdater_port
93 | root = 'http://{}:{}/api'.format(host_ip, port)
94 | super(DnsUpdaterApi, self).__init__(root, username='', password='', headers=None,
95 | resp_wrapper=_updater_resp_wrapper)
96 |
97 | def notify_update_named(self, group_name, group_conf_md5):
98 | return self.post_json('/notify_update/named', data={
99 | 'group_name': group_name,
100 | 'group_conf_md5': group_conf_md5
101 | })
102 |
103 | def notify_update(self, deploy_type, group_name, **kwargs):
104 | return self.post_json('/notify_update',
105 | data={
106 | 'update_type': deploy_type,
107 | 'group_name': group_name,
108 | 'params': kwargs
109 | })
110 |
--------------------------------------------------------------------------------
/dnsdb_common/library/database.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sqlite3
4 | from oslo_config import cfg
5 |
6 | from ..library import exception as err
7 | from ..library.log import getLogger
8 | log = getLogger(__name__)
9 |
10 | CONF = cfg.CONF
11 |
12 |
13 | class SqliteDatabase(object):
14 | def __init__(self):
15 | self.data = CONF.database.data
16 |
17 | def connect(self):
18 | try:
19 | self.conn = sqlite3.connect(self.data)
20 | self.cur = self.conn.cursor()
21 | return True
22 | except Exception as e:
23 | log.error("Unable to connect to db: %s" % (e))
24 | return False
25 |
26 | def execute_sql(self, sql):
27 | try:
28 | self.cur.execute(sql)
29 | return True
30 | except Exception as e:
31 | log.error("Failed to execute sql: %s" % (e))
32 | return False
33 |
34 | def fetchall(self):
35 | try:
36 | result = self.cur.fetchall()
37 | return True, result
38 | except Exception as e:
39 | log.error("Failed to fetchall: %s" % (e))
40 | return False, None
41 |
42 | def insert(self, sql):
43 | try:
44 | self.cur.execute(sql)
45 | return err.ENONE
46 | except sqlite3.IntegrityError as e:
47 | log.error("Failed to execute sql: %s" % e)
48 | return err.ECONFLICT
49 | except Exception as e:
50 | log.error("Failed to execute sql: %s" % e)
51 | return err.EDBGONE
52 |
53 | def execute_and_fetch(self, sql, *args, **kargs):
54 | try:
55 | self.cur.execute(sql, *args, **kargs)
56 | result = self.cur.fetchall()
57 | return True, result
58 | except Exception as e:
59 | log.error("Failed to execute_and_fetch sql: %s" % e)
60 | return False, None
61 |
62 | def execute_and_get_count(self, sql, *args, **kargs):
63 | try:
64 | self.cur.execute(sql, *args, **kargs)
65 | result = self.cur.rowcount
66 | return True, result
67 | except Exception as e:
68 | log.error("Failed to execute_and_get_count sql: %s" % (e))
69 | return False, None
70 |
71 | def commit(self):
72 | self.conn.commit()
73 |
74 | def commit_and_close(self):
75 | self.conn.commit()
76 | self.conn.close()
77 |
78 | def rollback(self):
79 | self.conn.rollback()
80 |
81 | def rollback_and_close(self):
82 | self.conn.rollback()
83 | self.conn.close()
84 |
85 | def get_rowcount(self):
86 | return self.cur.rowcount
87 |
88 | def close(self):
89 | self.conn.close()
90 |
91 |
92 | def get_db_connection():
93 | db = SqliteDatabase()
94 | if not db.connect():
95 | return None
96 | return db
97 |
--------------------------------------------------------------------------------
/dnsdb_common/library/email_util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import smtplib
4 | from email.header import Header
5 | from email.mime.text import MIMEText
6 |
7 | from oslo_config import cfg
8 |
9 | from ..library.log import getLogger
10 |
11 | log = getLogger(__name__)
12 |
13 | CONF = cfg.CONF
14 |
15 |
16 | def send_email(subject, content, sender=None, password=None, receivers=None):
17 | msg = ''
18 | try:
19 | if content is None:
20 | content = ""
21 | msg = MIMEText(content, 'plain', 'utf-8')
22 | if sender is None:
23 | sender = CONF.MAIL.from_addr
24 | password = CONF.MAIL.password
25 | elif not isinstance(sender, str):
26 | raise TypeError('sender should be str type.')
27 | if receivers is None:
28 | receivers = CONF.MAIL.info_list
29 | elif not isinstance(receivers, str):
30 | raise TypeError('Receivers should be str type.')
31 | to_list = receivers.split(';')
32 |
33 | msg['Subject'] = Header(subject, 'utf-8')
34 | msg['From'] = Header(sender, 'utf-8')
35 | msg['To'] = Header(receivers, 'utf-8')
36 | s = smtplib.SMTP()
37 | s.connect(CONF.MAIL.server, CONF.MAIL.port)
38 | if password:
39 | s.login(sender, password)
40 | s.sendmail(sender, to_list, msg.as_string())
41 | except Exception as e:
42 | log.error("Failed to send email:%s, because: %s" % (msg, e))
43 | finally:
44 | try:
45 | s.close()
46 | except:
47 | pass
48 |
49 |
50 | def send_alert_email(content, sender=None):
51 | receivers = CONF.MAIL.alert_list
52 | subject = "[DNSDB alarm]"
53 | send_email(subject, content, sender, receivers)
54 |
55 |
--------------------------------------------------------------------------------
/dnsdb_common/library/exception.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | BADREQUEST = 400
4 | UNAUTHORIZED = 401
5 | FORBIDDEN = 403
6 | GONE = 410
7 | TOOMANYREQUESTS = 412
8 |
9 |
10 | class DnsdbException(Exception):
11 | def __init__(self, message, errcode=500, detail=None, msg_ch=u''):
12 | self.message = message
13 | self.errcode = errcode
14 | self.detail = detail
15 | self.msg_ch = msg_ch
16 | super(DnsdbException, self).__init__()
17 |
18 | def __str__(self):
19 | return self.message
20 |
21 | def json(self):
22 | return dict(code=self.errcode, why=self.message)
23 |
24 |
25 | class Unauthorized(DnsdbException):
26 | def __init__(self, message='Unauthorized', errcode=UNAUTHORIZED, detail=None, msg_ch=u''):
27 | super(Unauthorized, self).__init__(message, errcode, detail, msg_ch)
28 |
29 | class Forbidden(DnsdbException):
30 | def __init__(self, message='Forbidden', errcode=FORBIDDEN, detail=None, msg_ch=u''):
31 | super(Forbidden, self).__init__(message, errcode, detail, msg_ch)
32 |
33 |
34 | class OperationLogErr(DnsdbException):
35 | def __init__(self, message, errcode=500, detail=None, msg_ch=u''):
36 | super(OperationLogErr, self).__init__(message, errcode, detail, msg_ch)
37 |
38 |
39 | class BadParam(DnsdbException):
40 | def __init__(self, message='Bad params', errcode=BADREQUEST, detail=None, msg_ch=u''):
41 | super(BadParam, self).__init__(message, errcode, detail, msg_ch)
42 |
43 |
44 | class UpdaterErr(DnsdbException):
45 | pass
46 |
47 |
48 | class ConfigErr(UpdaterErr):
49 | def __init__(self, message):
50 | super(ConfigErr, self).__init__(message=message, errcode=501)
51 |
--------------------------------------------------------------------------------
/dnsdb_common/library/gunicorn_app.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import multiprocessing
4 |
5 | from gunicorn import glogging
6 | from gunicorn.app import base
7 | from gunicorn.six import iteritems
8 |
9 |
10 | class GunicornLogger(glogging.Logger):
11 | def access(self, resp, req, environ, request_time):
12 | # ignore healthcheck
13 | if environ.get('RAW_URI') == '/healthcheck.html':
14 | return
15 | super(GunicornLogger, self).access(resp, req, environ, request_time)
16 |
17 |
18 | def number_of_workers():
19 | return (multiprocessing.cpu_count() * 2) + 1
20 |
21 |
22 | class GunicornApplication(base.BaseApplication):
23 | def __init__(self, app, options=None):
24 | self.options = options or {}
25 | self.application = app
26 | super(GunicornApplication, self).__init__()
27 |
28 | def load_config(self):
29 | config = dict([(key, value) for key, value in iteritems(self.options)
30 | if key in self.cfg.settings and value is not None])
31 | config['logger_class'] = GunicornLogger
32 | for key, value in iteritems(config):
33 | self.cfg.set(key.lower(), value)
34 |
35 | def load(self):
36 | return self.application
37 |
--------------------------------------------------------------------------------
/dnsdb_common/library/local.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 et
3 |
4 | # Copyright 2011 OpenStack Foundation.
5 | # All Rights Reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 | # not use this file except in compliance with the License. You may obtain
9 | # a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 | # License for the specific language governing permissions and limitations
17 | # under the License.
18 |
19 | """Local storage of variables using weak references"""
20 |
21 | import threading
22 | import weakref
23 |
24 |
25 | class WeakLocal(threading.local):
26 | def __getattribute__(self, attr):
27 | rval = super(WeakLocal, self).__getattribute__(attr)
28 | if rval:
29 | # NOTE(mikal): this bit is confusing. What is stored is a weak
30 | # reference, not the value itself. We therefore need to lookup
31 | # the weak reference and return the inner value here.
32 | rval = rval()
33 | return rval
34 |
35 | def __setattr__(self, attr, value):
36 | value = weakref.ref(value)
37 | return super(WeakLocal, self).__setattr__(attr, value)
38 |
39 |
40 | # NOTE(mikal): the name "store" should be deprecated in the future
41 | store = WeakLocal()
42 |
43 | # A "weak" store uses weak references and allows an object to fall out of scope
44 | # when it falls out of scope in the code that uses the thread local storage. A
45 | # "strong" store will hold a reference to the object so that it never falls out
46 | # of scope.
47 | weak_store = WeakLocal()
48 | strong_store = threading.local()
49 |
--------------------------------------------------------------------------------
/dnsdb_common/library/singleton.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | class Singleton(object):
4 | __instance = None
5 |
6 | def __new__(cls, *args, **kwargs):
7 | if not isinstance(cls.__instance, cls):
8 | cls.__instance = object.__new__(cls, *args, **kwargs)
9 | cls.__instance.init()
10 | return cls.__instance
11 |
12 | def init(self):
13 | pass
14 |
15 |
16 | class MetaSingleton(type):
17 | def __init__(self, *args, **kwargs):
18 | self.__instance = None
19 | super(MetaSingleton, self).__init__(*args, **kwargs)
20 |
21 | def __call__(self, *args, **kwargs):
22 | if self.__instance is None:
23 | self.__instance = super(MetaSingleton, self).__call__(*args, **kwargs)
24 | return self.__instance
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/dnsdb_common/library/validator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 |
5 | def _match_pattern(pattern, string):
6 | ptn = re.search(r'(%s)' % pattern, string)
7 | if ptn is None:
8 | return False
9 | if len(ptn.groups()) == 0:
10 | return False
11 | return ptn.group(1) == string
12 |
13 |
14 | def valid_string(s, min_len=None, max_len=None,
15 | allow_blank=False, auto_trim=True, pattern=None):
16 | """
17 | @param s str/unicode 要校验的字符串
18 | @param min_len None/int
19 | @param max_len None/int
20 | @param allow_blank boolean
21 | @param auto_trim boolean
22 | @:param pattern re.pattern
23 | @return boolean is_ok
24 | @return string/int value 若是ok,返回int值,否则返回错误信息
25 | """
26 | if s is None:
27 | return False, u'不能为None'
28 | if not isinstance(s, str):
29 | return False, u"参数类型需要是字符串"
30 | if auto_trim:
31 | s = s.strip()
32 | str_len = len(s)
33 | if not allow_blank and str_len < 1:
34 | return False, u"参数不允许为空"
35 | if max_len is not None and str_len > max_len:
36 | return False, u"参数长度需小于%d" % max_len
37 | if min_len is not None and str_len < min_len:
38 | return False, u"参数长度需大于 %d" % min_len
39 | if pattern is not None and s and not _match_pattern(pattern, s):
40 | return False, u'参数包含的字符: %s' % pattern
41 | return True, s
42 |
43 |
44 | def valid_int(s, min_value=None, max_value=None):
45 | """\
46 | @param s str/unicode 要校验的字符串
47 | @param min_value None/int
48 | @param max_value None/int
49 | @return boolean is_ok
50 | @return string/int value 若是ok,返回int值,否则返回错误信息
51 | """
52 | if s is None:
53 | return False, "cannot is None"
54 | if not isinstance(s, str):
55 | return False, "must a string value"
56 | s = int(s)
57 | if max_value is not None and s > max_value:
58 | return False, "%d must less than %d" % (s, max_value)
59 | if min_value is not None and s < min_value:
60 | return False, "%d must greater than %d" % (s, min_value)
61 | return True, s
62 |
--------------------------------------------------------------------------------
/dnsdb_fe/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/dnsdb_fe/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/dnsdb_fe/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/dnsdb_fe/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module'
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
13 | extends: 'standard',
14 | // required to lint *.vue files
15 | plugins: [
16 | 'html'
17 | ],
18 | // add your custom rules here
19 | 'rules': {
20 | // allow paren-less arrow functions
21 | 'arrow-parens': 0,
22 | // allow async-await
23 | 'generator-star-spacing': 0,
24 | // allow debugger during development
25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/dnsdb_fe/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserslist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/dnsdb_fe/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/__init__.py
--------------------------------------------------------------------------------
/dnsdb_fe/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | console.log(chalk.cyan(' Build complete.\n'))
30 | console.log(chalk.yellow(
31 | ' Tip: built files are meant to be served over an HTTP server.\n' +
32 | ' Opening index.html over file:// won\'t work.\n'
33 | ))
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var config = require('../config')
4 | if (!process.env.NODE_ENV) {
5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6 | }
7 |
8 | var opn = require('opn')
9 | var path = require('path')
10 | var express = require('express')
11 | var webpack = require('webpack')
12 | var proxyMiddleware = require('http-proxy-middleware')
13 | var webpackConfig = process.env.NODE_ENV === 'testing'
14 | ? require('./webpack.prod.conf')
15 | : require('./webpack.dev.conf')
16 |
17 | // default port where dev server listens for incoming traffic
18 | var port = process.env.PORT || config.dev.port
19 | // automatically open browser, if not set will be false
20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
21 | // Define HTTP proxies to your custom API backend
22 | // https://github.com/chimurai/http-proxy-middleware
23 | var proxyTable = config.dev.proxyTable
24 |
25 | var app = express()
26 | var compiler = webpack(webpackConfig)
27 |
28 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
29 | publicPath: webpackConfig.output.publicPath,
30 | quiet: true
31 | })
32 |
33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
34 | log: false,
35 | heartbeat: 2000
36 | })
37 | // force page reload when html-webpack-plugin template changes
38 | compiler.plugin('compilation', function (compilation) {
39 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
40 | hotMiddleware.publish({ action: 'reload' })
41 | cb()
42 | })
43 | })
44 |
45 | // proxy api requests
46 | Object.keys(proxyTable).forEach(function (context) {
47 | var options = proxyTable[context]
48 | if (typeof options === 'string') {
49 | options = { target: options }
50 | }
51 | app.use(proxyMiddleware(options.filter || context, options))
52 | })
53 |
54 | // handle fallback for HTML5 history API
55 | app.use(require('connect-history-api-fallback')())
56 |
57 | // serve webpack bundle output
58 | app.use(devMiddleware)
59 |
60 | // enable hot-reload and state-preserving
61 | // compilation error display
62 | app.use(hotMiddleware)
63 |
64 | // serve pure static assets
65 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
66 | app.use(staticPath, express.static('./static'))
67 |
68 | var uri = 'http://localhost:' + port
69 |
70 | var _resolve
71 | var readyPromise = new Promise(resolve => {
72 | _resolve = resolve
73 | })
74 |
75 | console.log('> Starting dev server...')
76 | devMiddleware.waitUntilValid(() => {
77 | console.log('> Listening at ' + uri + '\n')
78 | // when env is testing, don't need open it
79 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
80 | opn(uri)
81 | }
82 | _resolve()
83 | })
84 |
85 | var server = app.listen(port)
86 |
87 | module.exports = {
88 | ready: readyPromise,
89 | close: () => {
90 | server.close()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: options.sourceMap
20 | }
21 | }
22 |
23 | // generate loader string to be used with extract text plugin
24 | function generateLoaders (loader, loaderOptions) {
25 | var loaders = [cssLoader]
26 | if (loader) {
27 | loaders.push({
28 | loader: loader + '-loader',
29 | options: Object.assign({}, loaderOptions, {
30 | sourceMap: options.sourceMap
31 | })
32 | })
33 | }
34 |
35 | // Extract CSS when that option is specified
36 | // (which is the case during production build)
37 | if (options.extract) {
38 | return ExtractTextPlugin.extract({
39 | use: loaders,
40 | fallback: 'vue-style-loader'
41 | })
42 | } else {
43 | return ['vue-style-loader'].concat(loaders)
44 | }
45 | }
46 |
47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
48 | return {
49 | css: generateLoaders(),
50 | postcss: generateLoaders(),
51 | less: generateLoaders('less'),
52 | sass: generateLoaders('sass', { indentedSyntax: true }),
53 | scss: generateLoaders('sass'),
54 | stylus: generateLoaders('stylus'),
55 | styl: generateLoaders('stylus')
56 | }
57 | }
58 |
59 | // Generate loaders for standalone style files (outside of .vue)
60 | exports.styleLoaders = function (options) {
61 | var output = []
62 | var loaders = exports.cssLoaders(options)
63 | for (var extension in loaders) {
64 | var loader = loaders[extension]
65 | output.push({
66 | test: new RegExp('\\.' + extension + '$'),
67 | use: loader
68 | })
69 | }
70 | return output
71 | }
72 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | }),
12 | transformToRequire: {
13 | video: 'src',
14 | source: 'src',
15 | img: 'src',
16 | image: 'xlink:href'
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: './src/main.js'
13 | },
14 | output: {
15 | path: config.build.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: process.env.NODE_ENV === 'production'
18 | ? config.build.assetsPublicPath
19 | : config.dev.assetsPublicPath
20 | },
21 | resolve: {
22 | extensions: ['.js', '.vue', '.json'],
23 | alias: {
24 | 'vue$': 'vue/dist/vue.esm.js',
25 | '@': resolve('src')
26 | }
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.(js|vue)$/,
32 | loader: 'eslint-loader',
33 | enforce: 'pre',
34 | include: [resolve('src'), resolve('test')],
35 | options: {
36 | formatter: require('eslint-friendly-formatter')
37 | }
38 | },
39 | {
40 | test: /\.vue$/,
41 | loader: 'vue-loader',
42 | options: vueLoaderConfig
43 | },
44 | {
45 | test: /\.js$/,
46 | loader: 'babel-loader',
47 | include: [resolve('src'), resolve('test')]
48 | },
49 | {
50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
51 | loader: 'url-loader',
52 | options: {
53 | limit: 10000,
54 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
55 | }
56 | },
57 | {
58 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
59 | loader: 'url-loader',
60 | options: {
61 | limit: 10000,
62 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
63 | }
64 | },
65 | {
66 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
67 | loader: 'url-loader',
68 | options: {
69 | limit: 10000,
70 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
71 | }
72 | }
73 | ]
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 |
9 | // add hot-reload related code to entry chunks
10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
12 | })
13 |
14 | module.exports = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
17 | },
18 | // cheap-module-eval-source-map is faster for development
19 | devtool: '#cheap-module-eval-source-map',
20 | plugins: [
21 | new webpack.DefinePlugin({
22 | 'process.env': config.dev.env
23 | }),
24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
25 | new webpack.HotModuleReplacementPlugin(),
26 | new webpack.NoEmitOnErrorsPlugin(),
27 | // https://github.com/ampedandwired/html-webpack-plugin
28 | new HtmlWebpackPlugin({
29 | filename: 'index.html',
30 | template: 'index.html',
31 | inject: true
32 | }),
33 | new FriendlyErrorsPlugin()
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 |
12 | var env = process.env.NODE_ENV === 'testing'
13 | ? require('../config/test.env')
14 | : config.build.env
15 |
16 | var webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true
21 | })
22 | },
23 | devtool: config.build.productionSourceMap ? '#source-map' : false,
24 | output: {
25 | path: config.build.assetsRoot,
26 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
28 | },
29 | plugins: [
30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
31 | new webpack.DefinePlugin({
32 | 'process.env': env
33 | }),
34 | new webpack.optimize.UglifyJsPlugin({
35 | compress: {
36 | warnings: false
37 | },
38 | sourceMap: true
39 | }),
40 | // extract css into its own file
41 | new ExtractTextPlugin({
42 | filename: utils.assetsPath('css/[name].[contenthash].css')
43 | }),
44 | // Compress extracted CSS. We are using this plugin so that possible
45 | // duplicated CSS from different components can be deduped.
46 | new OptimizeCSSPlugin({
47 | cssProcessorOptions: {
48 | safe: true
49 | }
50 | }),
51 | // generate dist index.html with correct asset hash for caching.
52 | // you can customize output by editing /index.html
53 | // see https://github.com/ampedandwired/html-webpack-plugin
54 | new HtmlWebpackPlugin({
55 | filename: process.env.NODE_ENV === 'testing'
56 | ? 'index.html'
57 | : config.build.index,
58 | template: 'index.html',
59 | inject: true,
60 | minify: {
61 | removeComments: true,
62 | collapseWhitespace: true,
63 | removeAttributeQuotes: true
64 | // more options:
65 | // https://github.com/kangax/html-minifier#options-quick-reference
66 | },
67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
68 | chunksSortMode: 'dependency'
69 | }),
70 | // split vendor js into its own file
71 | new webpack.optimize.CommonsChunkPlugin({
72 | name: 'vendor',
73 | minChunks: function (module, count) {
74 | // any required modules inside node_modules are extracted to vendor
75 | return (
76 | module.resource &&
77 | /\.js$/.test(module.resource) &&
78 | module.resource.indexOf(
79 | path.join(__dirname, '../node_modules')
80 | ) === 0
81 | )
82 | }
83 | }),
84 | // extract webpack runtime and module manifest to its own file in order to
85 | // prevent vendor hash from being updated whenever app bundle is updated
86 | new webpack.optimize.CommonsChunkPlugin({
87 | name: 'manifest',
88 | chunks: ['vendor']
89 | }),
90 | // copy custom static assets
91 | new CopyWebpackPlugin([
92 | {
93 | from: path.resolve(__dirname, '../static'),
94 | to: config.build.assetsSubDirectory,
95 | ignore: ['.*']
96 | }
97 | ])
98 | ]
99 | })
100 |
101 | if (config.build.productionGzip) {
102 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
103 |
104 | webpackConfig.plugins.push(
105 | new CompressionWebpackPlugin({
106 | asset: '[path].gz[query]',
107 | algorithm: 'gzip',
108 | test: new RegExp(
109 | '\\.(' +
110 | config.build.productionGzipExtensions.join('|') +
111 | ')$'
112 | ),
113 | threshold: 10240,
114 | minRatio: 0.8
115 | })
116 | )
117 | }
118 |
119 | if (config.build.bundleAnalyzerReport) {
120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
122 | }
123 |
124 | module.exports = webpackConfig
125 |
--------------------------------------------------------------------------------
/dnsdb_fe/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | // This is the webpack config used for unit tests.
2 |
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseConfig = require('./webpack.base.conf')
7 |
8 | var webpackConfig = merge(baseConfig, {
9 | // use inline sourcemap for karma-sourcemap-loader
10 | module: {
11 | rules: utils.styleLoaders()
12 | },
13 | devtool: '#inline-source-map',
14 | resolveLoader: {
15 | alias: {
16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
18 | 'scss-loader': 'sass-loader'
19 | }
20 | },
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': require('../config/test.env')
24 | })
25 | ]
26 | })
27 |
28 | // no need for app entry during tests
29 | delete webpackConfig.entry
30 |
31 | module.exports = webpackConfig
32 |
--------------------------------------------------------------------------------
/dnsdb_fe/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/dnsdb_fe/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 8081,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/dnsdb_fe/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/dnsdb_fe/config/test.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var devEnv = require('./dev.env')
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"'
6 | })
7 |
--------------------------------------------------------------------------------
/dnsdb_fe/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dns管理系统
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/dnsdb_fe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dnsdb-fe",
3 | "version": "0.1.0",
4 | "description": "A Vue.js project",
5 | "author": "wanghuiwh.wang ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "node build/dev-server.js",
10 | "build": "node build/build.js",
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
12 | "e2e": "node test/e2e/runner.js",
13 | "test": "npm run unit && npm run e2e",
14 | "pack_prod": "npm run build && rm -rf ../dnsdb/static && cp -R ./dist/static ../dnsdb/static && cp ./dist/index.html ../dnsdb/templates/",
15 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
16 | },
17 | "dependencies": {
18 | "axios": "^0.18.1",
19 | "element-ui": "^2.4.8",
20 | "font-awesome": "^4.7.0",
21 | "cryptiles": ">=4.1.2",
22 | "http2": "^3.3.7",
23 | "less": "^3.9.0",
24 | "less-loader": "^4.0.5",
25 | "style-loader": "^0.19.1",
26 | "vue": "^2.5.17",
27 | "vue-resource": "^1.5.1",
28 | "vue-router": "^3.0.1",
29 | "vuex": "^3.0.1"
30 | },
31 | "devDependencies": {
32 | "autoprefixer": "^7.1.2",
33 | "babel-core": "^6.22.1",
34 | "babel-eslint": "^7.1.1",
35 | "babel-loader": "^7.1.1",
36 | "babel-plugin-istanbul": "^4.1.1",
37 | "babel-plugin-transform-runtime": "^6.22.0",
38 | "babel-preset-env": "^1.3.2",
39 | "babel-preset-stage-2": "^6.22.0",
40 | "babel-register": "^6.22.0",
41 | "chai": "^3.5.0",
42 | "chalk": "^2.0.1",
43 | "connect-history-api-fallback": "^1.3.0",
44 | "copy-webpack-plugin": "^4.0.1",
45 | "cross-env": "^5.0.1",
46 | "cross-spawn": "^5.0.1",
47 | "css-loader": "^0.28.7",
48 | "cssnano": "^3.10.0",
49 | "eslint": "^4.18.2",
50 | "eslint-config-standard": "^6.2.1",
51 | "eslint-friendly-formatter": "^3.0.0",
52 | "eslint-loader": "^3.0.0",
53 | "eslint-plugin-html": "^3.0.0",
54 | "eslint-plugin-promise": "^3.4.0",
55 | "eslint-plugin-standard": "^2.0.1",
56 | "eventsource-polyfill": "^0.9.6",
57 | "express": "^4.14.1",
58 | "extract-text-webpack-plugin": "^2.0.0",
59 | "file-loader": "^0.11.2",
60 | "friendly-errors-webpack-plugin": "^1.1.3",
61 | "html-webpack-plugin": "^2.28.0",
62 | "http-proxy-middleware": "^0.20.0",
63 | "inject-loader": "^3.0.0",
64 | "lolex": "^1.5.2",
65 | "mocha": "^5.2.0",
66 | "nightwatch": "^0.1.0",
67 | "opn": "^5.1.0",
68 | "optimize-css-assets-webpack-plugin": "^2.0.0",
69 | "ora": "^1.2.0",
70 | "phantomjs-prebuilt": "^2.1.14",
71 | "rimraf": "^2.6.0",
72 | "selenium-server": "^3.0.1",
73 | "semver": "^5.3.0",
74 | "shelljs": "^0.7.6",
75 | "sinon": "^2.1.0",
76 | "sinon-chai": "^2.8.0",
77 | "url-loader": "^0.5.8",
78 | "vue-loader": "^12.1.0",
79 | "vue-style-loader": "^3.0.1",
80 | "vue-template-compiler": "^2.5.13",
81 | "webpack": "^3.12.0",
82 | "webpack-bundle-analyzer": "^3.3.2",
83 | "webpack-dev-middleware": "^1.10.0",
84 | "webpack-hot-middleware": "^2.18.0",
85 | "webpack-merge": "^4.1.0"
86 | },
87 | "engines": {
88 | "node": ">= 4.0.0",
89 | "npm": ">= 3.0.0"
90 | },
91 | "browserslist": [
92 | "> 1%",
93 | "last 2 versions",
94 | "not ie <= 8"
95 | ]
96 | }
97 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
64 |
123 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/Menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 系统概览
7 | 普通域名
8 | View域名
9 | 配置管理
10 | 系统管理
11 | 操作日志
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
59 |
60 |
62 |
63 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/admin/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
登录
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 登录
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
92 |
93 |
132 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/conf/Conf.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
44 |
45 |
46 |
49 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/conf/HeaderEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{item}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 当前Zone:{{ curZoneName }}
18 |
19 |
20 |
21 | 检查
22 | 提交
23 |
24 |
25 |
26 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
115 |
116 |
122 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/preview/Preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
已迁移机房:
6 |
无
7 |
8 |
12 |
16 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
42 |
43 |
已迁移ACL:
44 |
无
45 |
46 |
50 |
54 |
57 |
58 | {{ viewIspDict[scope.row.origin_acl] }}
59 |
60 |
61 |
64 |
65 | {{ viewIspDict[scope.row.now_acl] }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
域名统计
74 |
75 |
79 |
82 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
139 |
140 |
167 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/record/Record.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
42 |
43 |
46 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/system/System.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
25 |
26 |
29 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/components/view/View.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import Element from 'element-ui'
7 | import VueResource from 'vue-resource'
8 | import Vuex from 'vuex'
9 | import 'element-ui/lib/theme-chalk/index.css'
10 | import 'font-awesome/css/font-awesome.min.css'
11 | import './style.css'
12 | import store from './store/view.js'
13 |
14 | Vue.use(Element)
15 | Vue.use(VueResource)
16 | Vue.use(Vuex)
17 | Vue.config.productionTip = false
18 |
19 | /* eslint-disable no-new */
20 | new Vue({
21 | el: '#app',
22 | store,
23 | router,
24 | template: '',
25 | components: { App }
26 | })
27 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Conf from '@/components/conf/Conf'
4 | import HostManager from '@/components/conf/HostManager'
5 | import ZoneManager from '@/components/conf/ZoneManager'
6 | import View from '@/components/view/View'
7 | import Record from '@/components/record/Record'
8 | import DnsLog from '@/components/log/DnsLog'
9 | import Preview from '@/components/preview/Preview'
10 | import Login from '@/components/admin/Login'
11 | import System from '@/components/system/System'
12 | import UserManager from '@/components/system/UserManager'
13 | import Menu from '@/components/Menu'
14 |
15 | Vue.use(Router)
16 |
17 | export default new Router({
18 | routes: [
19 | {
20 | path: '/login',
21 | name: 'login',
22 | component: Login
23 | },
24 | {
25 | path: '/',
26 | name: 'approot',
27 | component: Menu,
28 | children: [
29 | {
30 | path: 'system',
31 | component: System,
32 | children: [
33 | {
34 | path: 'user',
35 | component: UserManager
36 | }
37 | ]
38 | },
39 | {
40 | path: 'preview',
41 | name: 'preview',
42 | component: Preview
43 | },
44 | {
45 | path: 'view',
46 | name: 'view',
47 | component: View
48 | },
49 | {
50 | path: 'record',
51 | name: 'Record',
52 | component: Record
53 | },
54 | {
55 | path: 'conf',
56 | name: 'Conf',
57 | component: Conf,
58 | children: [
59 | {
60 | path: 'zone',
61 | component: ZoneManager
62 | },
63 | {
64 | path: 'hostgroup',
65 | component: HostManager
66 | }
67 | ]
68 | },
69 | {
70 | path: 'dnslog',
71 | name: 'DnsLog',
72 | component: DnsLog
73 | }
74 | ]
75 | }
76 | ]
77 | })
78 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/store/view.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | Vue.use(Vuex)
4 |
5 | export default new Vuex.Store({
6 | state: {
7 | menu_index: '/system',
8 | loading: false,
9 | activeTab: 'user',
10 | userName: ''
11 | },
12 | mutations: {
13 | changeTab (state, tabName) {
14 | state.activeTab = tabName
15 | console.log(tabName)
16 | },
17 | changeMenuIndex (state, idx) {
18 | console.log(idx)
19 | state.menu_index = idx
20 | },
21 | changeUsername (state, username) {
22 | state.userName = username
23 | }
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/dnsdb_fe/src/style.css:
--------------------------------------------------------------------------------
1 |
2 | hr {
3 | border-top:1px;
4 | margin-top: 15px;
5 | }
6 |
7 | .button {
8 | vertical-align: middle;
9 | }
10 |
11 | .tab-panel {
12 | background: white;
13 | padding-left: 20px;
14 | padding-right: 20px;
15 | padding-top: 10px;
16 | padding-bottom: 10px;
17 | }
18 |
19 | .small-title {
20 | line-height: 30px;
21 | height: 30px;
22 | font-size: 16px;
23 | padding-left: 5px;
24 | color: #428bca
25 | }
26 |
27 | .medium-title {
28 | color: #428bca;
29 | font-size: 22px;
30 | margin-right: 20px;
31 | vertical-align: middle;
32 | }
33 |
34 | .large-title {
35 | font-size: 24px;
36 | color: #428bca;
37 | }
38 |
39 | .small-table {
40 | margin-top: 10px;
41 | width: 900px;
42 | }
43 |
44 | .medium-table {
45 | margin-top: 10px;
46 | width: 1200px;
47 | }
48 |
49 | .medium-input {
50 | width: 200px;
51 | margin-right: 10px;
52 | }
53 |
54 | .large-input {
55 | width: 300px;
56 | margin-right: 10px;
57 | }
58 |
59 | .xlarge-input {
60 | width: 80%;
61 | margin-right: 10px;
62 | }
63 |
64 | .list-item {
65 | width:100%;
66 | line-height: 40px;
67 | cursor:pointer;
68 | height: 40px;
69 | display: inline-block;
70 | border-bottom: 1px solid lightgray;
71 | }
72 |
73 | .small-list-item {
74 | padding-left: 5px;
75 | margin-right: 15px;
76 | font-size:13px;
77 | cursor:pointer;
78 | height:30px;
79 | line-height:30px;
80 | border-bottom-width:1px;
81 | border-bottom-color: lightgray;
82 | border-bottom-style: solid;
83 | }
84 |
85 | .el-button--info {
86 | color: #fff;
87 | background-color: #409EFF;
88 | border-color: #409EFF;
89 | }
90 |
91 | .el-button--default {
92 | color: #409eff;
93 | background: #ecf5ff;
94 | border-color: #b3d8ff;
95 | }
96 |
97 | .el-table-add-row {
98 | margin-top: 10px;
99 | width: 100%;
100 | height: 34px;
101 | border: 1px dashed #c1c1cd;
102 | border-radius: 3px;
103 | cursor: pointer;
104 | justify-content: center;
105 | display: flex;
106 | line-height: 34px;
107 | }
108 |
--------------------------------------------------------------------------------
/dnsdb_fe/static/.!83440!favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.!83440!favicon.ico
--------------------------------------------------------------------------------
/dnsdb_fe/static/.!83441!favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.!83441!favicon.ico
--------------------------------------------------------------------------------
/dnsdb_fe/static/.!83442!favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.!83442!favicon.ico
--------------------------------------------------------------------------------
/dnsdb_fe/static/.!83443!favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.!83443!favicon.ico
--------------------------------------------------------------------------------
/dnsdb_fe/static/.!83444!favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.!83444!favicon.ico
--------------------------------------------------------------------------------
/dnsdb_fe/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/.gitkeep
--------------------------------------------------------------------------------
/dnsdb_fe/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/dnsdb_fe/static/favicon.ico
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SOURCEDIR = .
8 | BUILDDIR = _build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Configuration file for the Sphinx documentation builder.
4 | #
5 | # This file does only contain a selection of the most common options. For a
6 | # full list see the documentation:
7 | # http://www.sphinx-doc.org/en/master/config
8 |
9 | # -- Path setup --------------------------------------------------------------
10 |
11 | # If extensions (or modules to document with autodoc) are in another directory,
12 | # add these directories to sys.path here. If the directory is relative to the
13 | # documentation root, use os.path.abspath to make it absolute, like shown here.
14 | #
15 | # import os
16 | # import sys
17 | # sys.path.insert(0, os.path.abspath('.'))
18 |
19 | import sphinx_rtd_theme
20 |
21 | # -- Project information -----------------------------------------------------
22 |
23 | project = u'DNSDB'
24 | copyright = u'2018, Qunar.com'
25 | author = u'Qunar.com'
26 |
27 | # The short X.Y version
28 | version = u'0.1'
29 | # The full version, including alpha/beta/rc tags
30 | release = u'0.1'
31 |
32 |
33 | # -- General configuration ---------------------------------------------------
34 |
35 | # If your documentation needs a minimal Sphinx version, state it here.
36 | #
37 | # needs_sphinx = '1.0'
38 |
39 | # Add any Sphinx extension module names here, as strings. They can be
40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
41 | # ones.
42 | extensions = [
43 | ]
44 |
45 | # Add any paths that contain templates here, relative to this directory.
46 | templates_path = ['_templates']
47 |
48 | # The suffix(es) of source filenames.
49 | # You can specify multiple suffix as a list of string:
50 | #
51 | # source_suffix = ['.rst', '.md']
52 | source_suffix = '.rst'
53 |
54 | # The master toctree document.
55 | master_doc = 'index'
56 |
57 | # The language for content autogenerated by Sphinx. Refer to documentation
58 | # for a list of supported languages.
59 | #
60 | # This is also used if you do content translation via gettext catalogs.
61 | # Usually you set "language" from the command line for these cases.
62 | language = u'zh_cn'
63 |
64 | # List of patterns, relative to source directory, that match files and
65 | # directories to ignore when looking for source files.
66 | # This pattern also affects html_static_path and html_extra_path.
67 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store']
68 |
69 | # The name of the Pygments (syntax highlighting) style to use.
70 | pygments_style = None
71 |
72 |
73 | # -- Options for HTML output -------------------------------------------------
74 |
75 | # The theme to use for HTML and HTML Help pages. See the documentation for
76 | # a list of builtin themes.
77 | #
78 | html_theme = "sphinx_rtd_theme"
79 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
80 |
81 | # Theme options are theme-specific and customize the look and feel of a theme
82 | # further. For a list of options available for each theme, see the
83 | # documentation.
84 | #
85 | # html_theme_options = {}
86 |
87 | # Add any paths that contain custom static files (such as style sheets) here,
88 | # relative to this directory. They are copied after the builtin static files,
89 | # so a file named "default.css" will overwrite the builtin "default.css".
90 | html_static_path = ['_static']
91 |
92 | # Custom sidebar templates, must be a dictionary that maps document names
93 | # to template names.
94 | #
95 | # The default sidebars (for documents that don't match any pattern) are
96 | # defined by theme itself. Builtin themes are using these templates by
97 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
98 | # 'searchbox.html']``.
99 | #
100 | # html_sidebars = {}
101 |
102 |
103 | # -- Options for HTMLHelp output ---------------------------------------------
104 |
105 | # Output file base name for HTML help builder.
106 | htmlhelp_basename = 'DNSDBdoc'
107 |
108 |
109 | # -- Options for LaTeX output ------------------------------------------------
110 |
111 | latex_elements = {
112 | # The paper size ('letterpaper' or 'a4paper').
113 | #
114 | # 'papersize': 'letterpaper',
115 |
116 | # The font size ('10pt', '11pt' or '12pt').
117 | #
118 | # 'pointsize': '10pt',
119 |
120 | # Additional stuff for the LaTeX preamble.
121 | #
122 | # 'preamble': '',
123 |
124 | # Latex figure (float) alignment
125 | #
126 | # 'figure_align': 'htbp',
127 | }
128 |
129 | # Grouping the document tree into LaTeX files. List of tuples
130 | # (source start file, target name, title,
131 | # author, documentclass [howto, manual, or own class]).
132 | latex_documents = [
133 | (master_doc, 'DNSDB.tex', u'DNSDB Documentation',
134 | u'Qunar.com', 'manual'),
135 | ]
136 |
137 |
138 | # -- Options for manual page output ------------------------------------------
139 |
140 | # One entry per manual page. List of tuples
141 | # (source start file, name, description, authors, manual section).
142 | man_pages = [
143 | (master_doc, 'dnsdb', u'DNSDB Documentation',
144 | [author], 1)
145 | ]
146 |
147 |
148 | # -- Options for Texinfo output ----------------------------------------------
149 |
150 | # Grouping the document tree into Texinfo files. List of tuples
151 | # (source start file, target name, title, author,
152 | # dir menu entry, description, category)
153 | texinfo_documents = [
154 | (master_doc, 'DNSDB', u'DNSDB Documentation',
155 | author, 'DNSDB', 'One line description of project.',
156 | 'Miscellaneous'),
157 | ]
158 |
159 |
160 | # -- Options for Epub output -------------------------------------------------
161 |
162 | # Bibliographic Dublin Core info.
163 | epub_title = project
164 |
165 | # The unique identifier of the text. This can be a ISBN number
166 | # or the project homepage.
167 | #
168 | # epub_identifier = ''
169 |
170 | # A unique identification for the text.
171 | #
172 | # epub_uid = ''
173 |
174 | # A list of files that should not be packed into the epub file.
175 | epub_exclude_files = ['search.html']
176 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. DNSDB documentation master file, created by
2 | sphinx-quickstart on Mon Dec 10 17:00:36 2018.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to DNSDB's documentation!
7 | =================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 |
14 |
15 | Indices and tables
16 | ==================
17 |
18 | * :ref:`genindex`
19 | * :ref:`modindex`
20 | * :ref:`search`
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/etc/beta/common.conf:
--------------------------------------------------------------------------------
1 | [etc]
2 | env = beta
3 | secret_key = SecretKeyForFlask
4 |
5 | [log]
6 | log-dir = /var/log/open_dnsdb/
7 | debug = True
8 | verbose = True
9 |
10 | [DB]
11 | connection=sqlite:////usr/local/open_dnsdb/dnsdb.db
12 |
13 | [mail]
14 | server = mail.server.corp.com
15 | port = 25
16 | from_addr = dnsdb@corp.com
17 | password = xxxxxx
18 | alert_list = ops@corp.com;opsdev@corp.com
19 | info_list = ops@corp.com
20 |
21 | [web]
22 | base_url = /
23 | run-mode = werkzeug
24 | bind = 0.0.0.0
25 | debug = True
26 |
27 | [gunicorn]
28 | timeout=600
29 | workers=4
30 | worker_class = eventlet
31 | daemon = False
32 | loglevel = debug
33 | ignore_healthcheck_accesslog=True
34 | accesslog = /var/log/open_dnsdb/access.log
35 |
36 | [api]
37 | dnsdbapi_url=http://127.0.0.1:9001/api
38 | dnsupdater_port = 9000
39 |
--------------------------------------------------------------------------------
/etc/beta/dnsdb-updater.conf:
--------------------------------------------------------------------------------
1 | [etc]
2 | tmp_dir=/usr/local/open_dnsdb/tmp
3 | log_dir = /var/log/open_dnsdb
4 | backup_dir=/usr/local/backup
5 | pidfile=/usr/local/open_dnsdb/tmp/named_updater.pid
6 | zone_update_interval = 5
7 | allow_ip = 127.0.0.1
8 |
9 | [log]
10 | log-file = dnsdb_updater.log
11 |
12 | [web]
13 | port = 9000
14 |
15 | [gunicorn]
16 | bind = 0.0.0.0:9000
17 |
18 | [bind_default]
19 | named_dir = /var/named/chroot/etc
20 | zone_dir = /var/named/chroot/var/named
21 | acl_dir = /var/named/chroot/var/named
22 | named_checkconf = /usr/sbin/named-checkconf
23 | named_zonecheck = /usr/sbin/named-checkzone
24 | mkrdns = /sbin/mkrdns
25 | rndc = /usr/sbin/rndc
26 | # bind运行时的用户名和属组
27 | user = named
28 | group = named
29 |
--------------------------------------------------------------------------------
/etc/beta/dnsdb.conf:
--------------------------------------------------------------------------------
1 | [etc]
2 | allow_ip = 127.0.0.1
3 | secret_key = SessionKeyForFalsk
4 | header_template = /usr/local/open_dnsdb/etc/template/zone_header
5 |
6 | [log]
7 | log-file = dnsdb.log
8 |
9 | [web]
10 | port = 9001
11 |
12 | [gunicorn]
13 | bind = 0.0.0.0:9001
14 |
15 | [view]
16 | acl_groups = ViewSlave
17 | cname_ttl = 300
18 | view_zone = view.com
19 | normal_view = corp.com:corp.view.com
20 | normal_cname = corp.com:view.corp.com
21 |
--------------------------------------------------------------------------------
/etc/beta/supervisor-dnsdb.conf:
--------------------------------------------------------------------------------
1 | [program:open-dnsdb]
2 | directory=/usr/local/open_dnsdb/
3 | command=/usr/local/open_dnsdb/tools/with_venv.sh dnsdb beta dnsdb
4 | autostart=True ;; 是否开机自动启动
5 | autorestart=True ;; 是否挂了自动重启
6 | redirect_stderr=True ;; 是否把 stderr 定向到 stdout
7 | stopasgroup=True
8 |
--------------------------------------------------------------------------------
/etc/beta/supervisor-updater.conf:
--------------------------------------------------------------------------------
1 | [program:open-dnsdb-conf-updater]
2 | directory=/usr/local/open_dnsdb/
3 | command=/usr/local/open_dnsdb/tools/with_venv.sh dnsdb-conf-updater beta dnsdb-updater
4 | autostart=True ;; 是否开机自动启动
5 | autorestart=True ;; 是否挂了自动重启
6 | redirect_stderr=True ;; 是否把 stderr 定向到 stdout
7 | stopasgroup=True
8 |
9 |
10 | [program:open-dnsdb-zone-updater]
11 | directory=/usr/local/open_dnsdb/
12 | command=/usr/local/open_dnsdb/tools/with_venv.sh dnsdb-zone-updater beta dnsdb-updater
13 | autostart=True ;; 是否开机自动启动
14 | autorestart=True ;; 是否挂了自动重启
15 | redirect_stderr=True ;; 是否把 stderr 定向到 stdout
16 | stopasgroup=True
17 |
--------------------------------------------------------------------------------
/etc/template/zone_header:
--------------------------------------------------------------------------------
1 | $TTL 7200 ; 2 hours
2 | @ IN SOA localhost. root.localhost. (
3 | pre_serial ; Serial
4 | 3600 ; Refresh (1 hour)
5 | 900 ; Retry (15 minutes)
6 | 3600000 ; Expire (5 weeks 6 days 16 hours)
7 | 3600 ; Minimum (1 hour)
8 | )
9 | @ 2D IN NS localhost.
10 | $ORIGIN zone_name.
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | concurrent-log-handler==0.9.12
2 | Flask==1.0.2
3 | Flask-Login==0.4.1
4 | Flask-Migrate==2.4.0
5 | Flask-RESTful==0.3.7
6 | Flask-SQLAlchemy==2.3.2
7 | httplib2==0.12.1
8 | jsonschema==3.0.1
9 | oslo.config==6.8.1
10 | multiping==1.1.2
11 | SQLAlchemy==1.3.0
12 | requests==2.21.0
13 | Sphinx==1.8.4
14 | sphinx-rtd-theme==0.4.3
15 | gunicorn==19.9.0
16 | eventlet==0.24.1
17 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = dnsdb
3 | platforms = any
4 | license_file = LICENSE
5 | description = Open-source dns management platform based on bind running on Python 2.7.
6 | long_description = file: README.md
7 | keywords = dns, view
8 | classifier =
9 | Development Status :: 1 - Beta
10 | Intended Audience :: End Users
11 | Intended Audience :: Developers
12 | Operating System :: OS Independent
13 | Programming Language :: Python :: 2.7
14 | Topic :: dnsdb
15 |
16 | [files]
17 | packages =
18 | dnsdb_updater
19 | data_files =
20 |
21 | [entry_points]
22 | console_scripts =
23 | dnsdb-conf-updater = dns_updater.app:app_start
24 | dnsdb-zone-updater = dns_updater.updater:updater
25 | dnsdb = dnsdb:main
26 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 et
3 | #
4 |
5 | from setuptools import setup
6 |
7 | setup(
8 | setup_requires=['pbr'],
9 | pbr=True,
10 | )
11 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | testtools
2 | WebTest
3 | tox
4 | fixture
5 | mock
6 |
--------------------------------------------------------------------------------
/tools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qunarcorp/open_dnsdb/f717e9752f00a2937ff523c2ca120c2ebae57bca/tools/__init__.py
--------------------------------------------------------------------------------
/tools/install_venv.py:
--------------------------------------------------------------------------------
1 | # Copyright 2010 United States Government as represented by the
2 | # Administrator of the National Aeronautics and Space Administration.
3 | # All Rights Reserved.
4 | #
5 | # Copyright 2010 OpenStack Foundation
6 | # Copyright 2013 IBM Corp.
7 | #
8 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 | # not use this file except in compliance with the License. You may obtain
10 | # a copy of the License at
11 | #
12 | # http://www.apache.org/licenses/LICENSE-2.0
13 | #
14 | # Unless required by applicable law or agreed to in writing, software
15 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 | # License for the specific language governing permissions and limitations
18 | # under the License.
19 |
20 | from __future__ import print_function
21 |
22 | import os
23 | import sys
24 |
25 | import install_venv_common as install_venv
26 |
27 |
28 | def print_help(venv, root):
29 | help = """
30 | Nova development environment setup is complete.
31 |
32 | Nova development uses virtualenv to track and manage Python dependencies
33 | while in development and testing.
34 |
35 | To activate the Nova virtualenv for the extent of your current shell
36 | session you can run:
37 |
38 | $ source %s/bin/activate
39 |
40 | Or, if you prefer, you can run commands in the virtualenv on a case by case
41 | basis by running:
42 |
43 | $ %s/tools/with_venv.sh
44 |
45 | Also, make test will automatically use the virtualenv.
46 | """
47 | print(help % (venv, root))
48 |
49 |
50 | def main(argv):
51 | root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
52 |
53 | if os.environ.get('tools_path'):
54 | root = os.environ['tools_path']
55 | venv = os.path.join(root, '.venv')
56 | if os.environ.get('venv'):
57 | venv = os.environ['venv']
58 |
59 | pip_requires = os.path.join(root, 'requirements.txt')
60 | test_requires = os.path.join(root, 'test-requirements.txt')
61 | py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
62 | project = 'Nova'
63 | install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
64 | py_version, project)
65 | options = install.parse_args(argv)
66 | install.check_python_version()
67 | install.check_dependencies()
68 | install.create_virtualenv(no_site_packages=options.no_site_packages, python_interpreter=options.python_interpreter)
69 | install.install_dependencies()
70 | print_help(venv, root)
71 |
72 |
73 | if __name__ == '__main__':
74 | main(sys.argv)
75 |
--------------------------------------------------------------------------------
/tools/updater/pre_updater_start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PROJPATH=/usr/local/open-dnsdb
4 | MKDIR=/bin/mkdir
5 | CP=/bin/cp
6 | CHMOD=/bin/chmod
7 |
8 |
9 | $MKDIR $PROJPATH/tmp/var/named -p &>/dev/null
10 | $MKDIR $PROJPATH/tmp/etc -p &>/dev/null
11 |
12 | if [ ! -f "/sbin/mkrdns" ]; then
13 | $CP $PROJPATH/tools/mkrdns /sbin/
14 | $CHMOD +x /sbin/mkrdns
15 | fi
16 |
--------------------------------------------------------------------------------
/tools/with_venv.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | tools_path=${tools_path:-$(dirname $0)}
3 | APP_PATH=$(dirname $tools_path)
4 | venv_path=${venv_path:-${tools_path}}
5 | venv_dir=${venv_name:-/../.venv}
6 | TOOLS=${tools_path}
7 | VENV=${venv:-${venv_path}/${venv_dir}}
8 | if [ -n "$PYTHONPATH" ]; then
9 | export PYTHONPATH=$APP_PATH
10 | else
11 | export PYTHONPATH=$APP_PATH:$PYTHONPATH
12 | fi
13 | source ${VENV}/bin/activate && "$@"
14 |
--------------------------------------------------------------------------------