iddb now has a more generic source specification.
patch 1b457e1b596f50c1601c267966359ae9a119e4d1
Author: Goetz Pfeiffer <Goetz.Pfeiffer@helmholtz-berlin.de>
Date: Mon Jun 27 09:25:01 CEST 2022
* iddb now has a more generic source specification.
The data source is no longer hard-coded in the progrem but can be set by
command line options, an environment variable and a configuration file in the
"share" directory.
The configuration file allows specification of the data source dependent on the
hostname or the domain name.
hunk ./Makefile 710
+ echo "IDDB_SOURCE=$(IDDB_SOURCE)" >> $@
hunk ./bin/iddb 13
-# [_$_]
+#
hunk ./bin/iddb 18
-# [_$_]
+#
hunk ./bin/iddb 22
+# pylint: disable= too-many-lines
+
hunk ./bin/iddb 29
+import configparser
hunk ./bin/iddb 53
-HOST="gwc1c.acc.bessy.de"
-PORT=7643
+
+# environment variable IDDB_SOURCE:
+# server:gwc1c.acc.bessy.de:7643
+# file:/opt/OPI/idcp/id_db.SDCyml
+ENV_VAR_NAME="IDDB_SOURCE"
+
+#HOST="gwc1c.acc.bessy.de"
+#PORT=7643
hunk ./bin/iddb 73
+# read configuration file
+
+BII_SCRIPTS_CONFIG= os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "bii_scripts.config")
+
+CONFIG_FILE= "iddb.config"
+
+rx_def=re.compile(r'([^=]+)=(.*)')
+rx_comment=re.compile(r'^\s*#')
+
+def read_config(filename):
+ """read config file."""
+ if not os.path.exists(filename):
+ raise IOError("Error, file %s not found" % filename)
+ result= {}
+ with open(filename) as fp:
+ for line in fp:
+ line= line.strip()
+ if rx_comment.match(line):
+ continue
+ m= rx_def.match(line)
+ if not m:
+ continue
+ name= m.group(1)
+ val= m.group(2).strip()
+ val= os.path.expandvars(val)
+ result[name]= val
+ return result
+
+def read_biiscripts_config():
+ """read bii_scripts.config."""
+ if not os.path.exists(BII_SCRIPTS_CONFIG):
+ sys.stderr.write("Warning: File %s not found\n" % BII_SCRIPTS_CONFIG)
+ return {}
+ return read_config(BII_SCRIPTS_CONFIG)
+
+def get_share_dir():
+ """get SHARE_INSTALL_DIR."""
+ d= read_biiscripts_config()
+ if not d:
+ return None
+ return d["SHARE_INSTALL_DIR"]
+
+# -----------------------------------------------
+# read iddb config file
+
+def iddb_config():
+ """read iddb configuration."""
+ share_dir= get_share_dir()
+ if not share_dir:
+ return None
+ filename= os.path.join(share_dir, "bii_scripts", CONFIG_FILE)
+ if not os.path.exists(filename):
+ sys.stderr.write("Warning: File '%s' not found\n" % filename)
+ return None
+ config = configparser.ConfigParser()
+ config.read(filename)
+ return config
+
+# -----------------------------------------------
+# find sourcespec from hostname
+
+def sourcespec_by_hostname():
+ """get sourcespec by current hostname."""
+ def cget(config,section,key):
+ """read something from configuration data."""
+ if key not in config[section]:
+ return None
+ v= config[section][key].strip()
+ if not v:
+ return None
+ return v
+ # get configuration data from SHARE_INSTALL_DIR/bii_scripts/iddb.config:
+ config_data= iddb_config()
+ if not config_data:
+ return None
+ # get full qualified hostname:
+ fullhostname= socket.getfqdn()
+ # try to find it in the "HOSTS" section of the configuration:
+ if "HOSTS" in config_data:
+ spec= cget(config_data, "HOSTS", fullhostname)
+ if spec:
+ return spec
+ # examine the domain:
+ if "DOMAINS" in config_data:
+ domain= fullhostname
+ while "." in domain:
+ domain= domain.split(".", 1)[1]
+ spec= cget(config_data, "DOMAINS", domain)
+ if spec:
+ return spec
+ # finally examine if there is a default "*" in HOSTS section:
+ if "HOSTS" in config_data:
+ spec= cget(config_data, "HOSTS", "*")
+ if spec:
+ return spec
+ return None
+
+# -----------------------------------------------
+# parse server specification
+
+def parse_server(spec):
+ """parse a server-spec.
+
+ returns:
+ (host, port) # port: an integer
+ raises ValueError on error.
+ """
+ errmsg= "%s has not the form 'HOST:PORT'" % spec
+ l= spec.split(":")
+ if not l:
+ raise ValueError(errmsg)
+ if len(l)!=2:
+ raise ValueError(errmsg)
+ try:
+ l[1]= int(l[1])
+ except ValueError as e:
+ raise ValueError(errmsg) from e
+ return l
+
+def parse_spec(spec):
+ """parse source spec.
+
+ spec: source specification, one of:
+ server:HOST:PORT
+ file:FILENAME
+
+ returns one of:
+ { "host": HOST, "port": PORT}
+ { "filename": FILENAME}
+
+ or raises a ValueError exception
+ """
+ l= spec.split(":", maxsplit=1)
+ while True:
+ if not l:
+ break
+ if l[0] == "server":
+ try:
+ (host, port)= parse_server(l[1])
+ return {"host": host, "port": port }
+ except ValueError:
+ break
+ if l[0] == "file":
+ if len(l)==2:
+ return { "filename": l[1] }
+ break
+ break
+ raise ValueError("Error, cannot parse source spec '%s'" % spec)
+
+def parse_env():
+ """parse environment variable IDDB_SOURCE.
+
+ returns one of:
+ { "host": HOST, "port": PORT}
+ { "filename": FILENAME}
+ None
+ """
+ var= os.environ.get(ENV_VAR_NAME)
+ if var is None:
+ return None
+ try:
+ return parse_spec(var)
+ except ValueError as e:
+ st= str(e).replace("Error", "Warning")
+ sys.stderr.write(st)
+ return None
+
+# -----------------------------------------------
hunk ./bin/iddb 325
- # Note: Using "allow_none= True" in [_$_]
+ # Note: Using "allow_none= True" in
hunk ./bin/iddb 719
+ # pylint: disable=consider-using-dict-items
hunk ./bin/iddb 808
- global HOST, PORT
+ # pylint: disable= too-many-locals
hunk ./bin/iddb 817
+ source_spec= None
+
+ if args.file:
+ source_spec= {"filename": args.file }
+ if args.server:
+ if args.file:
+ sys.exit("contradicting options: -f --server")
+ try:
+ (host, port)= parse_server(args.server)
+ except ValueError as e:
+ sys.exit(str(e)+" (option --server)")
+ source_spec= { "host": host, "port": port }
+
+ # Try to parse data source from environment variable. parse_env() may
+ # return None:
+ if not source_spec:
+ source_spec= parse_env()
+
+ if not source_spec:
+ # reads iddb configuration file:
+ sp= sourcespec_by_hostname()
+ if sp:
+ try:
+ source_spec= parse_spec(sp)
+ except ValueError as e:
+ sys.exit(str(e)+" (configuration file)")
+
+ if not source_spec:
+ sys.exit(("Error, no source specified.\n"
+ "You must either:\n"
+ "- set environment variable '%s'\n"
+ "- provide a StructuredData file with option '-f'\n"
+ "- provide a server with option --server\n"
+ "- define %s in file:\n"
+ " %s") % \
+ (ENV_VAR_NAME, ENV_VAR_NAME, CONFIG_FILE))
+
hunk ./bin/iddb 855
- if args.file:
- dbobj= DbFile(args.file)
+ if "filename" in source_spec:
+ dbobj= DbFile(source_spec["filename"])
+ elif "host" in source_spec:
+ dbobj= DbXML(source_spec["host"], source_spec["port"])
hunk ./bin/iddb 860
- if args.server:
- errmsg= "%s has not the form 'HOST:PORT'" % args.server
- if not ":" in args.server:
- sys.exit(errmsg)
- l= args.server.split(":")
- if len(l)!=2:
- sys.exit(errmsg)
- HOST= l[0]
- try:
- PORT= int(l[1])
- except ValueError:
- sys.exit(errmsg)
- dbobj= DbXML(HOST, PORT)
+ raise AssertionError("unexpected sourcespec: %s" % repr(source_spec))
hunk ./bin/iddb 934
-Known commands:
+If no command is given, the program performs the "list" command.
+
+Commands
+--------
+
+Commands for undulator properties
++++++++++++++++++++++++++++++++++
+
hunk ./bin/iddb 973
-Raw StructuredData access (note: most options except "--id" are ignore
-for these commands).
-
-Option -i IDNAME causes "id_data.NAME." to be prepended to PATTERN, where [_$_]
-"NAME" is the StructuredData undulator name
+Commands for raw StructuredData access
+++++++++++++++++++++++++++++++++++++++
+
+Note: most options except "--id" are ignore for these commands.
+
+Note: Option -i IDNAME causes "id_data.NAME." to be prepended to PATTERN, where
+ "NAME" is the StructuredData undulator name
hunk ./bin/iddb 991
+Specification of the data source
+--------------------------------
+
+The data source is specified by order of precedence by these methods:
+
+ - options '-f' or '--server'
+ - environment variable 'IDDB_SOURCE'
+ - configuration file $SHARE_INSTALL_DIR/bii_scripts/iddb.config
+
+Environment variable 'IDDB_SOURCE'
+++++++++++++++++++++++++++++++++++
+
+ One of the strings:
+ - server:HOST:PORT
+ - file:FILENAME
+
+Configuration file
+++++++++++++++++++
+
+The configuration file is located at $SHARE_INSTALL_DIR/bii_scripts/iddb.config.
+$SHARE_INSTALL_DIR is the location of the 'share' directory.
+
+This is a file in windows-ini format. Source specifications have that same
+format as for the environment variable 'IDDB_SOURCE' (see above).
+
+Section [HOSTS] contains source specifications specific host names,
+ '*' is a default for all hosts.
+Section [DOMAINS] contains source specifications specific domains. Partial
+ matches of domains are supported, e.g. 'mycompany.com' matches
+ 'subnet1.mycompany.com'
+
+Further documentation
+---------------------
+
hunk ./bin/iddb 1031
-If no command is given, the program performs the "list" command.
hunk ./bin/iddb 1095
- help=("specify the StructuredData XML-RPC "
- "server in the form 'HOST:PORT'. The "
- "default is %s:%s") % \
- (HOST, PORT),
+ help="specify the StructuredData XML-RPC "
+ "server in the form 'HOST:PORT'",
hunk ./bin/iddb 1106
- "the XMLRPC server. Note: For this the "
+ "an XMLRPC server. Note: For this the "
hunk ./bin/iddb 1108
- "installed.",
+ "installed on your system.",
addfile ./share/bii_scripts/iddb.config
hunk ./share/bii_scripts/iddb.config 1
+# iddb source specifications for specific hosts or
+# domains.
+
+[HOSTS]
+# source specifications for hosts
+# '*' is a default when no other match is found
+
+* = [_$_]
+orange = server:gwc1c.acc.bessy.de:7643
+
+[DOMAINS]
+# source specifications for domains
+# these may also be partial domains, e.g.
+# "mycompany.com" matches "subnet1.mycompany.com"
+
+bessy.de = server:gwc1c.acc.bessy.de:7643