1# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15import glob 16import json 17import os 18import urllib 19 20import requests 21import yaml 22 23from openstack.config import _util 24from openstack import exceptions 25 26_VENDORS_PATH = os.path.dirname(os.path.realpath(__file__)) 27_VENDOR_DEFAULTS = {} 28_WELL_KNOWN_PATH = "{scheme}://{netloc}/.well-known/openstack/api" 29 30 31def _get_vendor_defaults(): 32 global _VENDOR_DEFAULTS 33 if not _VENDOR_DEFAULTS: 34 for vendor in glob.glob(os.path.join(_VENDORS_PATH, '*.yaml')): 35 with open(vendor, 'r') as f: 36 vendor_data = yaml.safe_load(f) 37 _VENDOR_DEFAULTS[vendor_data['name']] = vendor_data['profile'] 38 for vendor in glob.glob(os.path.join(_VENDORS_PATH, '*.json')): 39 with open(vendor, 'r') as f: 40 vendor_data = json.load(f) 41 _VENDOR_DEFAULTS[vendor_data['name']] = vendor_data['profile'] 42 return _VENDOR_DEFAULTS 43 44 45def get_profile(profile_name): 46 vendor_defaults = _get_vendor_defaults() 47 if profile_name in vendor_defaults: 48 return vendor_defaults[profile_name].copy() 49 profile_url = urllib.parse.urlparse(profile_name) 50 if not profile_url.netloc: 51 # This isn't a url, and we already don't have it. 52 return 53 well_known_url = _WELL_KNOWN_PATH.format( 54 scheme=profile_url.scheme, 55 netloc=profile_url.netloc, 56 ) 57 response = requests.get(well_known_url) 58 if not response.ok: 59 raise exceptions.ConfigException( 60 "{profile_name} is a remote profile that could not be fetched:" 61 " {status_code} {reason}".format( 62 profile_name=profile_name, 63 status_code=response.status_code, 64 reason=response.reason)) 65 vendor_defaults[profile_name] = None 66 return 67 vendor_data = response.json() 68 name = vendor_data['name'] 69 # Merge named and url cloud config, but make named config override the 70 # config from the cloud so that we can supply local overrides if needed. 71 profile = _util.merge_clouds( 72 vendor_data['profile'], 73 vendor_defaults.get(name, {})) 74 # If there is (or was) a profile listed in a named config profile, it 75 # might still be here. We just merged in content from a URL though, so 76 # pop the key to prevent doing it again in the future. 77 profile.pop('profile', None) 78 # Save the data under both names so we don't reprocess this, no matter 79 # how we're called. 80 vendor_defaults[profile_name] = profile 81 vendor_defaults[name] = profile 82 return profile 83