diff --git a/.copr/Makefile b/.copr/Makefile
new file mode 100644
index 0000000..919eb2b
--- /dev/null
+++ b/.copr/Makefile
@@ -0,0 +1,24 @@
+top=..
+
+all: srpm
+
+prereq: $(top)/rpmbuild
+ rpm -q git rpm-build >/dev/null || dnf -y install git rpm-build
+
+update-dist-tools: $(top)/dist-tools
+ ( cd "$(top)/dist-tools" && git pull )
+
+$(top)/dist-tools:
+ git clone https://github.com/jelu/dist-tools.git "$(top)/dist-tools"
+
+$(top)/rpmbuild:
+ mkdir -p "$(top)"/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
+
+srpm: prereq update-dist-tools
+ git config --global safe.directory "*"
+ test -f .gitmodules && git submodule update --init || true
+ echo "$(spec)" | grep -q "develop.spec" && auto_build_number=`date --utc +%s` message="Auto build `date --utc --iso-8601=seconds`" "$(top)/dist-tools/spec-new-changelog-entry" || true
+ overwrite=yes nosign=yes "$(top)/dist-tools/create-source-packages" rpm
+ cp ../*.orig.tar.gz "$(top)/rpmbuild/SOURCES/"
+ echo "$(spec)" | grep -q "develop.spec" && rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist rpm/*.spec || rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist "$(spec)"
+ cp "$(top)"/rpmbuild/SRPMS/*.src.rpm "$(outdir)"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..38cc1c4
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://www.dns-oarc.net/donate
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..985ca02
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "develop", "master" ]
+ pull_request:
+ branches: [ "develop" ]
+ schedule:
+ - cron: "41 0 * * 3"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ python ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-and-quality
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..805f7a8
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,32 @@
+name: "Test"
+
+on:
+ push:
+ branches: [ "develop" ]
+ pull_request:
+ branches: [ "develop" ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v3
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ sudo apt-get install python3-maxminddb python3-yaml wget
+ python -m pip install --upgrade pip
+ pip install .
+ - name: Test
+ run: |
+ cd tests
+ rm ipv4-address-space.csv ipv6-unicast-address-assignments.csv
+ wget https://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.csv
+ wget https://www.iana.org/assignments/ipv6-unicast-address-assignments/ipv6-unicast-address-assignments.csv
+ ./test.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a703a82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+/venv/
+
+*.pyc
+__pycache__/
+
+.pytest_cache/
+.coverage
+htmlcov/
+
+dist/
+build/
+*.egg-info/
+
+tests/test.out
+tests/test.*.tmp
+coverage.xml
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..99808c4
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,223 @@
+2024-06-18 Jerry Lundström
+
+ Release 1.4.2
+
+ This release fixes issues with IANA's IPv6 parameters file,
+ dsc-datatool expected a RIR in the Designation field but IANA recently
+ added a title for SRv6 reservation which caused an exception.
+
+ Other updates are related to packages and GitHub workflows.
+
+ 7560d82 Tests
+ 8568c84 Fix client subnet authority
+ a8c58a9 Workflow
+ fd8915c RPM SUSE
+
+2023-12-06 Jerry Lundström
+
+ Release 1.4.1
+
+ This release fixes issue with InfluxDB quoting, was missing to quote
+ the quote character.
+
+ Other changes:
+ - Dependency correction for SLE 15.5
+ - Tweaks to test layouts
+
+ b44b874 Tests
+ eef3ae0 SLE 15.5
+ 75c7fc1 Influx quoting
+
+2023-06-15 Jerry Lundström
+
+ Release 1.4.0
+
+ This release adds the option `--encoding` to set an encoding to use
+ for reading and writing files.
+
+ f64c8b6 encoding man-page
+ 09c0ce9 Encoding
+
+2022-11-10 Jerry Lundström
+
+ Release 1.3.0
+
+ This release adds option `nonstrict` to `client_subnet_authority`
+ generator for skipping bad data in datasets.
+
+ The contrib DSC+Grafana test site dashboards has been moved to its
+ own repository, feel free to contribute your own creations to it:
+ https://github.com/DNS-OARC/dsc-datatool-grafana
+
+ 90b232d Add CodeQL workflow for GitHub code scanning
+ e4fa3b0 Test site
+ 474f97d client_subnet_authority non-strict mode
+
+2022-06-13 Jerry Lundström
+
+ Release 1.2.0
+
+ This release fixes handling of base64'ed strings in DSC XML and will
+ now decode them back into text when reading, the selected output will
+ then handling any quoting or escaping needed.
+ Added a new option for Prometheus output to set a prefix for metrics so
+ that they can be easily separated from other metrics if needed, see
+ `man dsc-datatool-output prometheus`.
+
+ 5f9f972 Fix COPR
+ 3d72019 Prometheus metric prefix
+ bdc992e base64 labels
+
+2022-04-05 Jerry Lundström
+
+ Release 1.1.0
+
+ This release adds support for Prometheus' node_exporter using it's
+ Textfile Collector (see `man dsc-datatool-output prometheus`) and
+ fixes a bug in InfluxDB output when selecting what timestamp to use.
+ Also updates packages and Grafana test site dashboards.
+
+ 4381541 RPM
+ 19bc153 Typo/clarification
+ 2a32dd8 Prometheus, InfluxDB, Copyright
+ dd5323e debhelper
+ 7352c1e Bye Travis
+ 32b3bbe Grafana dashboards
+ 304ab76 Info
+
+2020-10-21 Jerry Lundström
+
+ Release 1.0.2
+
+ This release fixed a bug in DAT file parsing that was discovered when
+ adding coverage tests.
+
+ 45b1aa3 Coverage
+ 7aedc1a Coverage
+ 64957b9 DAT, Coverage
+ 370fb86 Coverage
+ 891cb7c Coverage
+ 9374faa Coverage
+
+2020-08-07 Jerry Lundström
+
+ Release 1.0.1
+
+ This release adds compatibility with Python v3.5 which allows
+ packages to be built for Ubuntu Xenial.
+
+ bc0be5b python 3.5
+
+2020-08-03 Jerry Lundström
+
+ Release 1.0.0
+
+ This release brings a complete rewrite of the tool, from Perl to
+ Python. This rewrite was made possible thanks to funding from EURid,
+ and will help with maintainability and packaging.
+
+ Core design and command line syntax is kept the same but as the
+ libraries the generators use have been changed additional command line
+ options must be used.
+
+ - client_subnet_authority (generator)
+
+ This generator now uses IANA's IP address space registry CSVs to
+ look up the network authority, therefor it needs either to fetch
+ the CSV files or be given them on command line.
+
+ See `man dsc-datatool-generator client_subnet_authority` for more
+ information.
+
+ - client_subnet_country (generator)
+
+ This generator now uses MaxMind databases to look up country based
+ on subnet.
+
+ See `man dsc-datatool generator client_subnet_country` for more
+ information and setup guide of the MaxMind databases.
+
+ 589ea8b Badges
+ c32038b nonstrict
+ 0ea3e32 LGTM
+ cff2e1c COPR
+ 02c31b0 COPR
+ e8332fd COPR
+ 6d9f71c Input, YAML
+ 93ba755 EPEL 8 packages
+ 3e2df6f Authority
+ f5d023f Debian packaging
+ 1a59f09 Documentation
+ 85cb1e1 restructure
+ decd3f6 man-pages, URLs
+ f264854 man-pages
+ d73c319 man-pages
+ f5ca007 man-pages
+ 7bfaf53 Fedora dependencies
+ 3452b48 RPM dependencies
+ 7a4edbc Test
+ ed43406 client_subnet_authority
+ 62c7d9d Server, node
+ e0c6419 RPM package
+ 938f154 Rewrite
+ 5400464 README
+ 968ccb1 COPR, spec
+ 14d987f RPM requires
+ ee10efb Package
+ a25870f Funding
+
+Revision history for App::DSC::DataTool
+
+0.05 2019-05-31
+ Release 0.05
+
+ Fixed issue with empty values in InfluxDB output, they are now
+ quoted as an empty string.
+
+ 9917c4e InfluxDB quote keys/values
+
+0.04 2019-01-21
+ Release 0.04
+
+ Package dependency fix and update of example Grafana dashboards.
+
+ d3babc9 Copyright years
+ 9955c88 Travis Perl versions
+ 134a8b3 Debian dependency
+ 2d2114d Fix #23: Rework Grafana dashboards to hopefully show more
+ correct numbers and also split them up.
+ 9bca0d3 Prepare SPEC for OSB/COPR
+
+0.03 2016-12-16
+ Release 0.03
+
+ Support processing of 25 of the 37 DAT files that the Extractor
+ can produce, the others can not be converted into time series data
+ since they lack timestamps. Processing of XML is the recommended
+ approach to secure all information.
+
+ 72e829c Implement processing of DAT directories
+ 45294d0 RPM spec
+ 4e8ff69 Fix 5.24 forbidden keys usage
+ 7589ad2 Use perl 5.24 also
+ cfac110 Fix #16: Handle directories in --xml and warn that --dat is
+ not implemented yet
+
+0.02 2016-11-11
+ Release 0.02
+
+ First release of `dsc-datatool` with support for:
+ - Reading DSC XML files
+ - Transformer:
+ - Labler: convert indexes/keys to textual names such as QTYPE
+ - ReRanger: (re)compile lists/ranges/buckets into new buckets
+ - NetRemap: (re)compile IP addresses and subets into new subnets
+ - Generator:
+ - client_subnet_authority: Create a dataset with IP Authority for subnets
+ - client_subnet_country: Create a dataset with Countries for subnets
+ - Output:
+ - Graphite
+ - InfluxDB
+
+ See `dsc-datatool -h` for options and wiki article:
+ https://github.com/DNS-OARC/dsc-datatool/wiki/Setting-up-a-test-Grafana
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6e6115d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,33 @@
+DSC DataTool
+
+Copyright (c) 2016-2024 OARC, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..7712d2c
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+graft dsc_datatool
+global-exclude *.pyc
+include CHANGES
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2ef09e3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# DSC DataTool
+
+[](https://lgtm.com/projects/g/DNS-OARC/dsc-datatool/alerts/) [](https://sonarcloud.io/dashboard?id=dns-oarc%3Adsc-datatool) [](https://sonarcloud.io/dashboard?id=dns-oarc%3Adsc-datatool)
+
+Tool for converting, exporting, merging and transforming DSC data.
+
+Please have a look at the man-page(s) `dsc-datatool` (1) on how to use or
+[the wiki article](https://github.com/DNS-OARC/dsc-datatool/wiki/Setting-up-a-test-Grafana)
+on how to set this up using Influx DB and Grafana.
+
+More information about DSC may be found here:
+- https://www.dns-oarc.net/tools/dsc
+- https://www.dns-oarc.net/oarc/data/dsc
+
+Issues should be reported here:
+- https://github.com/DNS-OARC/dsc-datatool/issues
+
+General support and discussion:
+- Mattermost: https://chat.dns-oarc.net/community/channels/oarc-software
+- mailing-list: https://lists.dns-oarc.net/mailman/listinfo/dsc
+
+## Dependencies
+
+`dsc-datatool` requires the following Python libraries:
+- PyYAML
+- maxminddb
+
+## Python Development Environment
+
+Using Ubuntu/Debian:
+
+```
+sudo apt-get install python3-maxminddb python3-yaml python3-venv
+python3 -m venv venv --system-site-packages
+. venv/bin/activate
+pip install -e . --no-deps
+```
diff --git a/contrib/iana-dns-params-toyaml.py b/contrib/iana-dns-params-toyaml.py
new file mode 100644
index 0000000..bf482c6
--- /dev/null
+++ b/contrib/iana-dns-params-toyaml.py
@@ -0,0 +1,44 @@
+import yaml
+import csv
+from urllib.request import Request, urlopen
+from io import StringIO
+
+rcode = {}
+qtype = {}
+opcode = {}
+
+for row in csv.reader(StringIO(urlopen(Request('http://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv')).read().decode('utf-8'))):
+ if row[0] == 'RCODE':
+ continue
+ rcode[row[0]] = row[1]
+
+for row in csv.reader(StringIO(urlopen(Request('http://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv')).read().decode('utf-8'))):
+ if row[0] == 'TYPE':
+ continue
+ qtype[row[1]] = row[0]
+
+for row in csv.reader(StringIO(urlopen(Request('http://www.iana.org/assignments/dns-parameters/dns-parameters-5.csv')).read().decode('utf-8'))):
+ if row[0] == 'OpCode':
+ continue
+ opcode[row[0]] = row[1]
+
+y = {}
+
+for n in ['rcode', 'client_addr_vs_rcode', 'rcode_vs_replylen']:
+ y[n] = { 'Rcode': {} }
+ for k, v in rcode.items():
+ y[n]['Rcode'][k] = v
+
+for n in ['qtype', 'transport_vs_qtype', 'certain_qnames_vs_qtype', 'qtype_vs_tld', 'qtype_vs_qnamelen', 'chaos_types_and_names', 'dns_ip_version_vs_qtype']:
+ y[n] = { 'Qtype': {} }
+ for k, v in qtype.items():
+ if v == '*':
+ v = 'wildcard'
+ y[n]['Qtype'][k] = v
+
+for n in ['opcode']:
+ y[n] = { 'Opcode': {} }
+ for k, v in rcode.items():
+ y[n]['Opcode'][k] = v
+
+print(yaml.dump(y, explicit_start=True, default_flow_style=False))
diff --git a/dsc_datatool/__init__.py b/dsc_datatool/__init__.py
new file mode 100644
index 0000000..e12b4dc
--- /dev/null
+++ b/dsc_datatool/__init__.py
@@ -0,0 +1,484 @@
+"""dsc_datatool
+
+The main Python module for the command line tool `dsc-datatool`, see
+`man dsc-datatool` on how to run it.
+
+On runtime it will load all plugins under the following module path:
+- dsc_datatool.input
+- dsc_datatool.output
+- dsc_datatool.generator
+- dsc_datatool.transformer
+
+Each plugin category should base it class on one of the follow superclasses:
+- dsc_datatool.Input
+- dsc_datatool.Output
+- dsc_datatool.Generator
+- dsc_datatool.Transformer
+
+Doing so it will be automatically registered as available and indexed in
+the following public dicts using the class name:
+- inputs
+- outputs
+- generators
+- transformers
+
+Example of an output:
+
+ from dsc_datatool import Output
+ class ExampleOutput(Output):
+ def process(self, datasets)
+ ...
+
+:copyright: 2024 OARC, Inc.
+"""
+
+__version__ = '1.4.2'
+
+import argparse
+import logging
+import os
+import importlib
+import pkgutil
+import sys
+import traceback
+import re
+
+args = argparse.Namespace()
+inputs = {}
+outputs = {}
+generators = {}
+transformers = {}
+process_dataset = {}
+encoding = 'utf-8'
+
+
+class Dataset(object):
+ """A representation of a DSC dataset
+
+ A DSC dataset is one to two dimensional structure where the last
+ dimension holds an array of values and counters.
+
+ It is based on the XML structure of DSC:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Attributes:
+ - name: The name of the dataset
+ - start_time: The start time of the dataset in seconds
+ - stop_time: The stop time of the dataset in seconds
+ - dimensions: An array with `Dimension`, the first dimension
+ """
+ name = None
+ start_time = None
+ stop_time = None
+ dimensions = None
+
+
+ def __init__(self):
+ self.dimensions = []
+
+
+ def __repr__(self):
+ return '' % (self.name, self.dimensions)
+
+
+class Dimension(object):
+ """A representation of a DSC dimension
+
+ A DSC dataset dimension which can be the first or second dimension,
+ see `Dataset` for more information.
+
+ Attributes:
+ - name: The name of the dimension
+ - value: Is set to the value of the dimension if it's the first dimension
+ - values: A dict of values with corresponding counters if it's the second dimension
+ """
+ name = None
+ value = None
+ values = None
+ dimensions = None
+
+
+ def __init__(self, name):
+ self.name = name
+ self.values = {}
+ self.dimensions = []
+
+
+ def __repr__(self):
+ return '' % (self.name, self.values or self.value, self.dimensions)
+
+
+class Input(object):
+ """Base class of an input plugin"""
+
+
+ def process(self, file):
+ """Input.process(...) -> [ Dataset, ... ]
+
+ Called to process a file and return an array of `Dataset`'s found in it.
+ """
+ raise Exception('process() not overloaded')
+
+
+ def __init_subclass__(cls):
+ """This method is called when a class is subclassed and it will
+ register the input plugin in `inputs`."""
+ global inputs
+ if cls.__name__ in inputs:
+ raise Exception('Duplicate input module: %s already exists' % cls.__name__)
+ inputs[cls.__name__] = cls
+
+
+class Output(object):
+ """Base class of an output plugin"""
+
+
+ def process(self, datasets):
+ """Output.process([ Dataset, ... ])
+
+ Called to output the `Dataset`'s in the given array."""
+ raise Exception('process() not overloaded')
+
+
+ def __init__(self, opts):
+ """instance = Output({ 'opt': value, ... })
+
+ Called to create an instance of the output plugin, will get a dict
+ with options provided on command line."""
+ pass
+
+
+ def __init_subclass__(cls):
+ """This method is called when a class is subclassed and it will
+ register the output plugin in `outputs`."""
+ global outputs
+ if cls.__name__ in outputs:
+ raise Exception('Duplicate output module: %s already exists' % cls.__name__)
+ outputs[cls.__name__] = cls
+
+
+class Generator(object):
+ """Base class of a generator plugin"""
+
+
+ def process(self, datasets):
+ """Generator.process([ Dataset, ... ]) -> [ Dataset, ... ]
+
+ Called to generate additional `Dataset`'s based on the given array
+ of `Dataset`'s."""
+ raise Exception('process() not overloaded')
+
+
+ def __init__(self, opts):
+ """instance = Generator({ 'opt': value, ... })
+
+ Called to create an instance of the generator plugin, will get a dict
+ with options provided on command line."""
+ pass
+
+
+ def __init_subclass__(cls):
+ """This method is called when a class is subclassed and it will
+ register the generator plugin in `generators`."""
+ global generators
+ if cls.__name__ in generators:
+ raise Exception('Duplicate generator module: %s already exists' % cls.__name__)
+ generators[cls.__name__] = cls
+
+
+class Transformer(object):
+ """Base class of a transformer plugin"""
+
+
+ def process(self, datasets):
+ """Transformer.process([ Dataset, ... ])
+
+ Called to do transformation of the given `Dataset`'s, as in modifying
+ them directly."""
+ raise Exception('process() not overloaded')
+
+
+ def __init__(self, opts):
+ """instance = Transformer({ 'opt': value, ... })
+
+ Called to create an instance of the transformer plugin, will get a dict
+ with options provided on command line."""
+ pass
+
+
+ def __init_subclass__(cls):
+ """This method is called when a class is subclassed and it will
+ register the transformer plugin in `transformers`."""
+ global transformers
+ if cls.__name__ in transformers:
+ raise Exception('Duplicate transformer module: %s already exists' % cls.__name__)
+ transformers[cls.__name__] = cls
+
+
+def main():
+ """Called when running `dsc-datatool`."""
+ def iter_namespace(ns_pkg):
+ return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")
+
+
+ def split_arg(arg, num=1):
+ sep = arg[0]
+ p = arg.split(sep)
+ p.pop(0)
+ ret = ()
+ while num > 0:
+ ret += (p.pop(0),)
+ num -= 1
+ ret += (p,)
+ return ret
+
+
+ def parse_opts(opts):
+ ret = {}
+ for opt in opts:
+ p = opt.split('=', maxsplit=1)
+ if len(p) > 1:
+ if p[0] in ret:
+ if isinstance(ret[p[0]], list):
+ ret[p[0]].append(p[1])
+ else:
+ ret[p[0]] = [ ret[p[0]], p[1] ]
+ else:
+ ret[p[0]] = p[1]
+ elif len(p) > 0:
+ ret[p[0]] = True
+ return ret
+
+
+ def _process(datasets, generators, transformers, outputs):
+ gen_datasets = []
+ for generator in generators:
+ try:
+ gen_datasets += generator.process(datasets)
+ except Exception as e:
+ logging.warning('Generator %s failed: %s' % (generator, e))
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ for tb in traceback.format_tb(exc_traceback):
+ logging.warning(str(tb))
+ return 2
+
+ datasets += gen_datasets
+
+ if '*' in transformers:
+ for transformer in transformers['*']:
+ try:
+ transformer.process(datasets)
+ except Exception as e:
+ logging.warning('Transformer %s failed: %s' % (transformer, e))
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ for tb in traceback.format_tb(exc_traceback):
+ logging.warning(str(tb))
+ return 2
+ for dataset in datasets:
+ if dataset.name in transformers:
+ for transformer in transformers[dataset.name]:
+ try:
+ transformer.process([dataset])
+ except Exception as e:
+ logging.warning('Transformer %s failed: %s' % (transformer, e))
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ for tb in traceback.format_tb(exc_traceback):
+ logging.warning(str(tb))
+ return 2
+
+ for output in outputs:
+ try:
+ output.process(datasets)
+ except Exception as e:
+ logging.warning('Output %s failed: %s' % (output, e))
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ for tb in traceback.format_tb(exc_traceback):
+ logging.warning(str(tb))
+ return 2
+
+ return 0
+
+
+ global args, inputs, outputs, generators, transformers, process_dataset
+
+ parser = argparse.ArgumentParser(prog='dsc-datatool',
+ description='Export DSC data into various formats and databases.',
+ epilog='See man-page dsc-datatool(1) and dsc-datatool-[generator|transformer|output] (5) for more information')
+ parser.add_argument('-c', '--conf', nargs=1,
+ help='Not implemented')
+ # help='Specify the YAML configuration file to use (default to ~/.dsc-datatool.conf), any command line option will override the options in the configuration file. See dsc-datatool.conf(5)for more information.')
+ parser.add_argument('-s', '--server', nargs=1,
+ help='Specify the server for where the data comes from. (required)')
+ parser.add_argument('-n', '--node', nargs=1,
+ help='Specify the node for where the data comes from. (required)')
+ parser.add_argument('-x', '--xml', action='append',
+ help='Read DSC data from the given file or directory, can be specified multiple times. If a directory is given then all files ending with .xml will be read.')
+ parser.add_argument('-d', '--dat', action='append',
+ help='Read DSC data from the given directory, can be specified multiple times. Note that the DAT format is depended on the filename to know what type of data it is.')
+ parser.add_argument('--dataset', action='append',
+ help='Specify that only the list of datasets will be processed, the list is comma separated and the option can be given multiple times.')
+ parser.add_argument('-o', '--output', action='append',
+ help='"