Skip to content

Commit

Permalink
Merge pull request #269 from NewFuture/feat-cli-config
Browse files Browse the repository at this point in the history
feat(config): supports cli argument and environment variables  to set config
  • Loading branch information
NewFuture committed Jun 23, 2021
2 parents cd4c7de + 9e54d4c commit 35ee01f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 57 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@

## 详细配置

所有字段可通过三种方式进行配置

1. 命令行参数 `ddns --key=value` (`ddns -h` 查看详情),优先级最高
2. JSON配置文件(值为null认为是有效值,会覆盖环境变量的设置,如果没有对应的key则会尝试试用环境变量)
3. 环境变量DDNS_前缀加上key 全大写或者全小写 (`${ddns_key}``${DDNS_KEY}`)

<details open>

<summary markdown="span">config.json 配置文件
Expand Down
69 changes: 12 additions & 57 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
@modified: rufengsuixing
"""
from __future__ import print_function
from argparse import ArgumentParser, RawTextHelpFormatter
from json import load as loadjson, dump as dumpjson
from time import ctime
from os import path, environ, stat, name as os_name
from os import path, environ, name as os_name
from tempfile import gettempdir
from logging import DEBUG, basicConfig, info, warning, error, debug
from subprocess import check_output
Expand All @@ -18,6 +16,7 @@

from util import ip
from util.cache import Cache
from util.config import init_config, get_config

__version__ = "${BUILD_SOURCEBRANCHNAME}@${BUILD_DATE}" # CI 时会被Tag替换
__description__ = "automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]"
Expand All @@ -38,48 +37,6 @@
CACHE_FILE = path.join(gettempdir(), 'ddns.cache')


def get_config(key=None, default=None, path="config.json"):
"""
读取配置
"""
if not hasattr(get_config, "config"):
try:
with open(path) as configfile:
get_config.config = loadjson(configfile)
get_config.time = stat(path).st_mtime
except IOError:
error(' Config file `%s` does not exist!' % path)
with open(path, 'w') as configfile:
configure = {
"$schema": "https://ddns.newfuture.cc/schema/v2.8.json",
"id": "YOUR ID or EMAIL for DNS Provider",
"token": "YOUR TOKEN or KEY for DNS Provider",
"dns": "dnspod",
"ipv4": [
"newfuture.cc",
"ddns.newfuture.cc"
],
"ipv6": [
"newfuture.cc",
"ipv6.ddns.newfuture.cc"
],
"index4": "default",
"index6": "default",
"ttl": None,
"proxy": None,
"debug": False,
}
dumpjson(configure, configfile, indent=2, sort_keys=True)
sys.stdout.write("New template configure file `%s` is generated.\n" % path)
sys.exit(1)
except:
sys.exit('fail to load config from file: %s' % path)
if key:
return get_config.config.get(key, default)
else:
return get_config.config


def get_ip(ip_type, index="default"):
"""
get IP address
Expand Down Expand Up @@ -135,10 +92,12 @@ def update_ip(ip_type, cache, dns, proxy_list):
domains = get_config(ipname)
if not domains:
return None
if not isinstance(domains, list):
domains = domains.strip('; ').replace(',',';').replace(' ',';').split(';')
index_rule = get_config('index' + ip_type, "default") # 从配置中获取index配置
address = get_ip(ip_type, index_rule)
if not address:
error('Fail to get %s address!' ,ipname)
error('Fail to get %s address!', ipname)
return False
elif cache and (address == cache[ipname]):
print('.', end=" ") # 缓存命中
Expand All @@ -157,14 +116,7 @@ def main():
"""
更新
"""
parser = ArgumentParser(description=__description__,
epilog=__doc__, formatter_class=RawTextHelpFormatter)
parser.add_argument('-v', '--version',
action='version', version=__version__)
parser.add_argument('-c', '--config',
default="config.json", help="run with config file [配置文件路径]")
config_file = parser.parse_args().config
get_config(path=config_file)
init_config(__description__, __doc__, __version__)
# Dynamicly import the dns module as configuration
dns_provider = str(get_config('dns', 'dnspod').lower())
dns = getattr(__import__('dns', fromlist=[dns_provider]), dns_provider)
Expand All @@ -177,16 +129,19 @@ def main():
level=DEBUG,
format='%(asctime)s <%(module)s.%(funcName)s> %(lineno)d@%(pathname)s \n[%(levelname)s] %(message)s')
print("DDNS[", __version__, "] run:", os_name, sys.platform)
print("Configuration was loaded from <==", path.abspath(config_file))
if get_config("config"):
print("Configuration was loaded from <==",
path.abspath(get_config("config")))
print("=" * 25, ctime(), "=" * 25, sep=' ')

proxy = get_config('proxy') or 'DIRECT'
proxy_list = proxy.strip('; ') .split(';')
proxy_list = proxy if isinstance(
proxy, list) else proxy.strip('; ').replace(',',';').split(';')

cache = get_config('cache', True) and Cache(CACHE_FILE)
if cache is False:
info("Cache is disabled!")
elif get_config.time >= cache.time:
elif get_config("config_modified_time") is None or get_config("config_modified_time") >= cache.time:
warning("Cache file is out of dated.")
cache.clear()
elif not cache:
Expand Down
122 changes: 122 additions & 0 deletions util/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from argparse import ArgumentParser, ArgumentTypeError, Namespace, RawTextHelpFormatter
from json import load as loadjson, dump as dumpjson
from logging import error
from os import stat, environ
from time import time

import sys

__cli_args = {} # type: Namespace
__config = {} # type: dict


def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise ArgumentTypeError('Boolean value expected.')


def init_config(description, doc, version):
global __cli_args
"""
配置
"""
parser = ArgumentParser(description=description,
epilog=doc, formatter_class=RawTextHelpFormatter)
parser.add_argument('-v', '--version',
action='version', version=version)
parser.add_argument('-c', '--config', help="run with config file [配置文件路径]")

# 参数定义
parser.add_argument('--dns', help="DNS Provider [DNS服务提供商]", choices=[
'alidns', 'cloudflare', 'dnscom', 'dnspod', 'dnspod_com', 'he', 'huaweidns', 'callback'])
parser.add_argument('--id', help="api ID [授权账户]")
parser.add_argument('--token', help="api token or Secret key [授权访问凭证或密钥]")
parser.add_argument('--ipv4', nargs="*",
help="ipv4 domain list [IPV4域名列表]")
parser.add_argument('--ipv6', nargs="*",
help="ipv6 domain list [IPV6域名列表]")
parser.add_argument('--index4', help="the way to get ipv4 [IPV4 获取方式]")
parser.add_argument('--index6', help="the way to get ipv6 [IPV6获取方式]")
parser.add_argument('--ttl', type=int, help="ttl for DNS [DNS 解析 TTL 时间]")
parser.add_argument('--proxy', nargs="*",
help="https proxy [设置http 代理,多代理逐个尝试直到成功]")
parser.add_argument('--debug', type=str2bool, nargs='?',
const=True, help="debug mode [是否开启调试,默认否]", )
parser.add_argument('--cache', type=str2bool, nargs='?',
const=True, help="eusing cache [是否缓存记录,默认是]")

__cli_args = parser.parse_args()
is_configfile_optional = get_config("token") or get_config("id")
config_file = get_config("config")
if not is_configfile_optional or config_file is not None:
__load_config(config_file or "config.json", is_configfile_optional)
__cli_args.config = config_file or "config.json"


def __load_config(path="config.json", skip_auto_generation=False):
"""
加载配置
"""
global __config, config_modified_time
try:
with open(path) as configfile:
__config = loadjson(configfile)
__config["config_modified_time"] = stat(path).st_mtime
except IOError:
if skip_auto_generation:
__config["config_modified_time"] = time()
return
error(' Config file `%s` does not exist!' % path)
with open(path, 'w') as configfile:
configure = {
"$schema": "https://ddns.newfuture.cc/schema/v2.8.json",
"id": "YOUR ID or EMAIL for DNS Provider",
"token": "YOUR TOKEN or KEY for DNS Provider",
"dns": "dnspod",
"ipv4": [
"newfuture.cc",
"ddns.newfuture.cc"
],
"ipv6": [
"newfuture.cc",
"ipv6.ddns.newfuture.cc"
],
"index4": "default",
"index6": "default",
"ttl": None,
"proxy": None,
"debug": False,
}
dumpjson(configure, configfile, indent=2, sort_keys=True)
sys.stdout.write(
"New template configure file `%s` is generated.\n" % path)
sys.exit(1)
except:
sys.exit('fail to load config from file: %s' % path)


def get_config(key, default=None):
"""
读取配置
1. 命令行参数
2. 配置文件
3. 环境变量
"""
if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None:
return getattr(__cli_args, key)
if key in __config:
return __config.get(key)
env_name = 'DDNS_'+key.upper() # type:str
if env_name in environ: # 大写环境变量
return environ.get(env_name)
if env_name.lower() in environ: # 小写环境变量
return environ.get(env_name.lower())
return default

0 comments on commit 35ee01f

Please sign in to comment.