1# Copyright 2018 Red Hat, Inc.
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 importlib
16import warnings
17
18import os_service_types
19
20from openstack import _log
21from openstack import service_description
22
23_logger = _log.setup_logging('openstack')
24_service_type_manager = os_service_types.ServiceTypes()
25
26
27def make_names():
28    imports = ['from openstack import service_description']
29    services = []
30
31    for service in _service_type_manager.services:
32        service_type = service['service_type']
33        if service_type == 'ec2-api':
34            # NOTE(mordred) It doesn't make any sense to use ec2-api
35            # from openstacksdk. The credentials API calls are all calls
36            # on identity endpoints.
37            continue
38        desc_class = _find_service_description_class(service_type)
39
40        st = service_type.replace('-', '_')
41
42        if desc_class.__module__ != 'openstack.service_description':
43            base_mod, dm = desc_class.__module__.rsplit('.', 1)
44            imports.append(
45                'from {base_mod} import {dm}'.format(
46                    base_mod=base_mod,
47                    dm=dm))
48        else:
49            dm = 'service_description'
50
51        dc = desc_class.__name__
52        services.append(
53            "{st} = {dm}.{dc}(service_type='{service_type}')".format(
54                st=st, dm=dm, dc=dc, service_type=service_type),
55        )
56
57        # Register the descriptor class with every known alias. Don't
58        # add doc strings though - although they are supported, we don't
59        # want to give anybody any bad ideas. Making a second descriptor
60        # does not introduce runtime cost as the descriptors all use
61        # the same _proxies dict on the instance.
62        for alias_name in _get_aliases(st):
63            if alias_name[-1].isdigit():
64                continue
65            services.append(
66                '{alias_name} = {st}'.format(
67                    alias_name=alias_name,
68                    st=st))
69        services.append('')
70    print("# Generated file, to change, run tools/print-services.py")
71    for imp in sorted(imports):
72        print(imp)
73    print('\n')
74    print("class ServicesMixin:\n")
75    for service in services:
76        if service:
77            print("    {service}".format(service=service))
78        else:
79            print()
80
81
82def _get_aliases(service_type, aliases=None):
83    # We make connection attributes for all official real type names
84    # and aliases. Three services have names they were called by in
85    # openstacksdk that are not covered by Service Types Authority aliases.
86    # Include them here - but take heed, no additional values should ever
87    # be added to this list.
88    # that were only used in openstacksdk resource naming.
89    LOCAL_ALIASES = {
90        'baremetal': 'bare_metal',
91        'block_storage': 'block_store',
92        'clustering': 'cluster',
93    }
94    all_types = set(_service_type_manager.get_aliases(service_type))
95    if aliases:
96        all_types.update(aliases)
97    if service_type in LOCAL_ALIASES:
98        all_types.add(LOCAL_ALIASES[service_type])
99    all_aliases = set()
100    for alias in all_types:
101        all_aliases.add(alias.replace('-', '_'))
102    return all_aliases
103
104
105def _find_service_description_class(service_type):
106    package_name = 'openstack.{service_type}'.format(
107        service_type=service_type).replace('-', '_')
108    module_name = service_type.replace('-', '_') + '_service'
109    class_name = ''.join(
110        [part.capitalize() for part in module_name.split('_')])
111    try:
112        import_name = '.'.join([package_name, module_name])
113        service_description_module = importlib.import_module(import_name)
114    except ImportError as e:
115        # ImportWarning is ignored by default. This warning is here
116        # as an opt-in for people trying to figure out why something
117        # didn't work.
118        warnings.warn(
119            "Could not import {service_type} service description: {e}".format(
120                service_type=service_type, e=str(e)),
121            ImportWarning)
122        return service_description.ServiceDescription
123    # There are no cases in which we should have a module but not the class
124    # inside it.
125    service_description_class = getattr(service_description_module, class_name)
126    return service_description_class
127
128
129make_names()
130