From d3ffa853bc2d00fad854b2f334a7c0eb6854d0ca Mon Sep 17 00:00:00 2001 From: Martin Levy Date: Wed, 15 Jan 2020 11:51:49 -0800 Subject: [PATCH] Added support for profiles - see README --- CloudFlare/cloudflare.py | 22 +++++++++++-------- CloudFlare/read_configs.py | 22 ++++++++++++++----- README.md | 43 ++++++++++++++++++++++++++++++++++++ README.rst | 45 ++++++++++++++++++++++++++++++++++++++ cli4/cli4.py | 12 ++++++++-- 5 files changed, 128 insertions(+), 16 deletions(-) diff --git a/CloudFlare/cloudflare.py b/CloudFlare/cloudflare.py index 4808f0a..d562045 100644 --- a/CloudFlare/cloudflare.py +++ b/CloudFlare/cloudflare.py @@ -19,7 +19,7 @@ class CloudFlare(object): class _v4base(object): """ Cloudflare v4 API""" - def __init__(self, email, token, certtoken, base_url, debug, raw, use_sessions): + def __init__(self, email, token, certtoken, base_url, debug, raw, use_sessions, profile): """ Cloudflare v4 API""" self.email = email @@ -28,6 +28,7 @@ def __init__(self, email, token, certtoken, base_url, debug, raw, use_sessions): self.base_url = base_url self.raw = raw self.use_sessions = use_sessions + self.profile = profile self.session = None self.user_agent = user_agent() @@ -821,13 +822,16 @@ def api_list(self, m=None, s=''): w = w + self.api_list(a, s + '/' + n) return w - def __init__(self, email=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True): + def __init__(self, email=None, token=None, certtoken=None, debug=False, raw=False, use_sessions=True, profile=None): """ Cloudflare v4 API""" base_url = BASE_URL # class creation values override configuration values - [conf_email, conf_token, conf_certtoken, extras] = read_configs() + try: + [conf_email, conf_token, conf_certtoken, extras] = read_configs(profile) + except: + raise CloudFlareAPIError(0, 'profile/configuration read error') if email is None: email = conf_email @@ -842,7 +846,7 @@ def __init__(self, email=None, token=None, certtoken=None, debug=False, raw=Fals token = None if certtoken == '': certtoken = None - self._base = self._v4base(email, token, certtoken, base_url, debug, raw, use_sessions) + self._base = self._v4base(email, token, certtoken, base_url, debug, raw, use_sessions, profile) # add the API calls api_v4(self) @@ -871,21 +875,21 @@ def __str__(self): if self._base.email is None: return '["%s"]' % ('REDACTED') else: - return '["%s","%s"]' % (self._base.email, 'REDACTED') + return '["%s","%s","%s"]' % (self._base.profile, self._base.email, 'REDACTED') def __repr__(self): """ Cloudflare v4 API""" if self._base.email is None: - return '%s,%s(%s,"%s","%s",%s,"%s")' % ( + return '%s,%s(%s,"%s","%s","%s",%s,"%s")' % ( self.__module__, type(self).__name__, - 'REDACTED', 'REDACTED', + self._base.profile, 'REDACTED', 'REDACTED', self._base.base_url, self._base.raw, self._base.user_agent ) else: - return '%s,%s(%s,"%s","%s","%s",%s,"%s")' % ( + return '%s,%s(%s,"%s","%s","%s","%s",%s,"%s")' % ( self.__module__, type(self).__name__, - self._base.email, 'REDACTED', 'REDACTED', + self._base.profile, self._base.email, 'REDACTED', 'REDACTED', self._base.base_url, self._base.raw, self._base.user_agent ) diff --git a/CloudFlare/read_configs.py b/CloudFlare/read_configs.py index 51bb1d3..acb21c7 100644 --- a/CloudFlare/read_configs.py +++ b/CloudFlare/read_configs.py @@ -7,7 +7,7 @@ except ImportError: import configparser as ConfigParser # py3 -def read_configs(): +def read_configs(profile=None): """ reading the config file for Cloudflare API""" # envioronment variables override config files @@ -24,27 +24,39 @@ def read_configs(): os.path.expanduser('~/.cloudflare/cloudflare.cfg') ]) + if profile is None: + profile = 'CloudFlare' + + if len(config.sections()) == 0: + ## no config file found - so env values (even if empty) should be returned + ## this isn't an error + return [email, token, certtoken, extras] + + if profile not in config.sections(): + ## section is missing - this is an error + raise + if email is None: try: - email = re.sub(r"\s+", '', config.get('CloudFlare', 'email')) + email = re.sub(r"\s+", '', config.get(profile, 'email')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): email = None if token is None: try: - token = re.sub(r"\s+", '', config.get('CloudFlare', 'token')) + token = re.sub(r"\s+", '', config.get(profile, 'token')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): token = None if certtoken is None: try: - certtoken = re.sub(r"\s+", '', config.get('CloudFlare', 'certtoken')) + certtoken = re.sub(r"\s+", '', config.get(profile, 'certtoken')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): certtoken = None if extras is None: try: - extras = re.sub(r"\s+", ' ', config.get('CloudFlare', 'extras')) + extras = re.sub(r"\s+", ' ', config.get(profile, 'extras')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): extras = None diff --git a/README.md b/README.md index 7769a24..2516c7b 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,9 @@ import CloudFlare # An authenticated call using an API Key and CA-Origin info cf = CloudFlare.CloudFlare(email='user@example.com', token='00000000000000000000000000000000', certtoken='v1.0-...') + + # An authenticated call using using a stored profile (see below) + cf = CloudFlare.CloudFlare(profile="CompanyX")) ``` If the account email and API key are not passed when you create the class, then they are retrieved from either the users exported shell environment variables or the .cloudflare.cfg or ~/.cloudflare.cfg or ~/.cloudflare/cloudflare.cfg files, in that order. @@ -203,6 +206,7 @@ If you're using an API Token, any `cloudflare.cfg` file must either not contain There is one call that presently doesn't need any email or token certification (the */ips* call); hence you can test without any values saved away. ### Using shell environment variables + ```bash $ export CF_API_EMAIL='user@example.com' # Do not set if using an API Token $ export CF_API_KEY='00000000000000000000000000000000' @@ -224,6 +228,45 @@ extras = $ ``` +More than one profile can be stored within that file. +Here's an example for a work and home setup (in this example work has an API Token and home uses email/token). + +```bash +$ cat ~/.cloudflare/cloudflare.cfg +[Work] +token = 00000000000000000000000000000000 +[Home] +email = home@example.com +token = 00000000000000000000000000000000 +$ +``` + +To select a profile, use the `--profile profile-name` option for `cli4` command or use `profile="profile-name"` in the library call. + +```bash +$ cli4 --profile Work /zones | jq '.[]|.name' | wc -l + 13 +$ + +$ cli4 --profile Home /zones | jq '.[]|.name' | wc -l + 1 +$ +``` + +Here is the same in code. + +```python +#!/usr/bin/env python + +import CloudFlare + +def main(): + cf = CloudFlare.CloudFlare(profile="Work") + ... +``` + +### About /certificates and certtoken + The *CF_API_CERTKEY* or *certtoken* values are used for the Origin-CA */certificates* API calls. You can leave *certtoken* in the configuration with a blank value (or omit the option variable fully). diff --git a/README.rst b/README.rst index aafcdde..bb7fbad 100644 --- a/README.rst +++ b/README.rst @@ -220,6 +220,9 @@ parameters. # An authenticated call using an API Key and CA-Origin info cf = CloudFlare.CloudFlare(email='user@example.com', token='00000000000000000000000000000000', certtoken='v1.0-...') + # An authenticated call using using a stored profile (see below) + cf = CloudFlare.CloudFlare(profile="CompanyX")) + If the account email and API key are not passed when you create the class, then they are retrieved from either the users exported shell environment variables or the .cloudflare.cfg or ~/.cloudflare.cfg or @@ -261,6 +264,48 @@ Using configuration file to store email and keys extras = $ +More than one profile can be stored within that file. Here's an example +for a work and home setup (in this example work has an API Token and +home uses email/token). + +.. code:: bash + + $ cat ~/.cloudflare/cloudflare.cfg + [Work] + token = 00000000000000000000000000000000 + [Home] + email = home@example.com + token = 00000000000000000000000000000000 + $ + +To select a profile, use the ``--profile profile-name`` option for +``cli4`` command or use ``profile="profile-name"`` in the library call. + +.. code:: bash + + $ cli4 --profile Work /zones | jq '.[]|.name' | wc -l + 13 + $ + + $ cli4 --profile Home /zones | jq '.[]|.name' | wc -l + 1 + $ + +Here is the same in code. + +.. code:: python + + #!/usr/bin/env python + + import CloudFlare + + def main(): + cf = CloudFlare.CloudFlare(profile="Work") + ... + +About /certificates and certtoken +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The *CF\_API\_CERTKEY* or *certtoken* values are used for the Origin-CA */certificates* API calls. You can leave *certtoken* in the configuration with a blank value (or omit the option variable fully). diff --git a/cli4/cli4.py b/cli4/cli4.py index 37d1a1e..09b5b4e 100644 --- a/cli4/cli4.py +++ b/cli4/cli4.py @@ -213,6 +213,7 @@ def do_it(args): output = 'json' raw = False dump = False + profile = None method = 'GET' usage = ('usage: cli4 ' @@ -220,18 +221,20 @@ def do_it(args): + '[-j|--json] [-y|--yaml] [-n|ndjson]' + '[-r|--raw] ' + '[-d|--dump] ' + + '[-p|--profile profile-name] ' + '[--get|--patch|--post|--put|--delete] ' + '[item=value|item=@filename|@filename ...] ' + '/command...') try: opts, args = getopt.getopt(args, - 'VhvqjyrdGPOUD', + 'Vhvqjyrdp:GPOUD', [ 'version', 'help', 'verbose', 'quiet', 'json', 'yaml', 'ndjson', 'raw', 'dump', + 'profile=', 'get', 'patch', 'post', 'put', 'delete' ]) except getopt.GetoptError: @@ -257,6 +260,8 @@ def do_it(args): output = 'ndjson' elif opt in ('-r', '--raw'): raw = True + elif opt in ('-p', '--profile'): + profile = arg; elif opt in ('-d', '--dump'): dump = True elif opt in ('-G', '--get'): @@ -363,7 +368,10 @@ def do_it(args): exit(usage) command = args[0] - cf = CloudFlare.CloudFlare(debug=verbose, raw=raw) + try: + cf = CloudFlare.CloudFlare(debug=verbose, raw=raw, profile=profile) + except Exception as e: + exit(e) results = run_command(cf, method, command, params, content, files) write_results(results, output)