Enable keystone token caching by OSC

SDK uses python keyring library to enable token caching. Normally this
is requiring a proper desktop (interactive) session, but there are some
backend plugins working in non-interactive mode. Store cache in an
unencrypted file on FS (this is not worse than storing passwords in
plaintext).

Change-Id: I42d698f15db5918443073fff8f27b926126d1d0f
diff --git a/functions-common b/functions-common
index c57c4cc..03d7c96 100644
--- a/functions-common
+++ b/functions-common
@@ -1047,6 +1047,8 @@
             --description="$3" \
             -f value -c id
     )
+    # Drop cached token to invalidate catalog info in the token
+    remove_token_cache
     echo $service_id
 }
 
@@ -1064,7 +1066,6 @@
         endpoint_id=$(openstack --os-cloud devstack-system-admin endpoint create \
             $1 $2 $3 --region $4 -f value -c id)
     fi
-
     echo $endpoint_id
 }
 
@@ -1088,6 +1089,8 @@
     if [[ -n "$5" ]]; then
         _get_or_create_endpoint_with_interface $1 internal $5 $2
     fi
+    # Drop cached token to invalidate catalog info in the token
+    remove_token_cache
     # return the public id to indicate success, and this is the endpoint most likely wanted
     echo $public_id
 }
@@ -2517,6 +2520,11 @@
     [ "$fips" == "1" ]
 }
 
+function remove_token_cache {
+    # Remove Keyring cache file
+    rm ~/.local/share/python_keyring/keyring_pass.cfg
+}
+
 # Restore xtrace
 $_XTRACE_FUNCTIONS_COMMON
 
diff --git a/lib/libraries b/lib/libraries
index 9ea3230..146434e 100755
--- a/lib/libraries
+++ b/lib/libraries
@@ -138,6 +138,10 @@
     # doesn't pull in etcd3.
     pip_install etcd3
     pip_install etcd3gw
+
+    # Add libraries required for token caching by OpenStackSDK/CLI
+    pip_install keyring
+    pip_install keyrings.alt
 }
 
 # Restore xtrace
diff --git a/tools/update_clouds_yaml.py b/tools/update_clouds_yaml.py
index 74dcdb2..9189882 100755
--- a/tools/update_clouds_yaml.py
+++ b/tools/update_clouds_yaml.py
@@ -30,7 +30,9 @@
             self._clouds_path = os.path.expanduser(
                 '~/.config/openstack/clouds.yaml')
             self._create_directory = True
-        self._clouds = {}
+        self._keyringrc_path = os.path.expanduser(
+                '~/.config/python_keyring/keyringrc.cfg')
+        self._config = {}
 
         self._cloud = args.os_cloud
         self._cloud_data = {
@@ -65,14 +67,17 @@
     def _read_clouds(self):
         try:
             with open(self._clouds_path) as clouds_file:
-                self._clouds = yaml.safe_load(clouds_file)
+                self._config = yaml.safe_load(clouds_file)
         except IOError:
             # The user doesn't have a clouds.yaml file.
             print("The user clouds.yaml file didn't exist.")
-            self._clouds = {}
+        if "cache" not in self._config:
+            # Enable auth (and only auth) caching. Currently caching into the
+            # file on FS is configured in `_write_clouds` function.
+            self._config["cache"] = {"auth": True}
 
     def _update_clouds(self):
-        self._clouds.setdefault('clouds', {})[self._cloud] = self._cloud_data
+        self._config.setdefault('clouds', {})[self._cloud] = self._cloud_data
 
     def _write_clouds(self):
 
@@ -81,7 +86,19 @@
             os.makedirs(clouds_dir)
 
         with open(self._clouds_path, 'w') as clouds_file:
-            yaml.dump(self._clouds, clouds_file, default_flow_style=False)
+            yaml.dump(self._config, clouds_file, default_flow_style=False)
+
+        # Enable keyring token caching
+        keyringrc_dir = os.path.dirname(self._keyringrc_path)
+        os.makedirs(keyringrc_dir, exist_ok=True)
+
+        # Configure auth caching into the file on FS. We do not bother of any
+        # expiration since SDK is smart enough to reauth once the token becomes
+        # invalid.
+        with open(self._keyringrc_path, 'w') as keyringrc_file:
+            keyringrc_file.write("[backend]\n")
+            keyringrc_file.write(
+                "default-keyring=keyrings.alt.file.PlaintextKeyring\n")
 
 
 def main():