File: //snap/google-cloud-cli/current/lib/surface/meta/apis/regen.py
# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command that regenerates existing or new gcloud API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
import fnmatch
import os
import re
import shutil
import googlecloudsdk
from googlecloudsdk import third_party
from googlecloudsdk.api_lib.regen import generate
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import parser_errors
from googlecloudsdk.command_lib.meta import regen as regen_utils
from googlecloudsdk.core import log
from googlecloudsdk.core.util import encoding
from googlecloudsdk.core.util import files
import ruamel.yaml
import six
from six.moves import map
_API_REGEX = '([a-z0-9_]+)/([a-z0-9_]+)'
@base.UniverseCompatible
class Regen(base.Command):
"""Regenerate given API(s) in gcloud."""
@staticmethod
def Args(parser):
parser.add_argument(
'api',
type=arg_parsers.ArgList(),
help='The APIs to regenerate in api_name/api_version format. '
'These can be filename glob expressions to regenerate multiple '
'apis. For example */* to regegenerate all apis and versions, '
'or */*beta* to only regenerate existing beta apis. '
'Note that if discovery doc is supplied this cannot '
'contain any wildcards.')
parser.add_argument(
'--api-discovery-doc',
help='Path to json file describing the api. If not specified, '
'an existing discovery doc will be used.')
parser.add_argument('--config',
help='Regeneration config yaml filename. '
'If not specified canonical config will be used.')
parser.add_argument(
'--base-dir',
help='Directory where generated code will be written. '
'By default googlecloudsdk/generated_clients/apis.')
def Run(self, args):
config = _LoadConfig(args.config)
root_dir = config['root_dir']
changed_config = False
if args.api_discovery_doc:
if not os.path.isfile(args.api_discovery_doc):
raise regen_utils.DiscoveryDocError(
'File not found {}'.format(args.api_discovery_doc))
if len(args.api) != 1:
raise parser_errors.ArgumentError(
'Can only specify one api when discovery doc is provided.')
match = re.match(_API_REGEX, args.api[0])
if not match:
raise regen_utils.DiscoveryDocError(
'Api name must match {} pattern when discovery doc '
'is specified'.format(_API_REGEX))
api_name, api_version = match.group(1), match.group(2)
if api_name not in config['apis']:
log.warning('No such api %s in config, adding...', api_name)
config['apis'][api_name] = {api_version: {'discovery_doc': ''}}
changed_config = True
elif api_version not in config['apis'][api_name]:
log.warning('No such api version %s in config, adding...', api_version)
config['apis'][api_name][api_version] = {'discovery_doc': ''}
changed_config = True
api_version_config = config['apis'].get(api_name).get(api_version, {})
discovery_doc = api_name + '_' + api_version + '.json'
new_discovery_doc = os.path.realpath(args.api_discovery_doc)
old_discovery_doc = os.path.realpath(
os.path.join(args.base_dir, root_dir, discovery_doc))
if new_discovery_doc != old_discovery_doc:
log.status.Print('Copying in {}'.format(new_discovery_doc))
shutil.copyfile(new_discovery_doc, old_discovery_doc)
if api_version_config['discovery_doc'] != discovery_doc:
changed_config = True
api_version_config['discovery_doc'] = discovery_doc
regenerate_list = [
(match.group(1), match.group(2), api_version_config)
]
else:
regex_patern = '|'.join(map(fnmatch.translate, args.api))
regenerate_list = [
(api_name, api_version, api_config)
for api_name, api_version_config in six.iteritems(config['apis'])
for api_version, api_config in six.iteritems(api_version_config)
if re.match(regex_patern, api_name + '/' + api_version)
]
if not regenerate_list:
raise regen_utils.UnknownApi(
'api [{api_name}] not found in "apis" section of '
'{config_file}. Use [gcloud meta apis list] to see available apis.'
.format(api_name=','.join(args.api),
config_file=args.config))
base_dir = args.base_dir or os.path.dirname(
os.path.dirname(googlecloudsdk.__file__))
for api_name, api_version, api_config in sorted(regenerate_list):
log.status.Print(
'Generating {} {} from {}'
.format(api_name,
api_version,
os.path.join(root_dir, api_config['discovery_doc'])))
discovery_doc = os.path.join(
base_dir, root_dir, api_config['discovery_doc'])
output_dir = os.path.join(base_dir, root_dir)
root_package = root_dir.replace('/', '.')
generate.GenerateApitoolsApi(
discovery_doc, output_dir, root_package, api_name, api_version,
api_config)
generate.GenerateApitoolsResourceModule(
discovery_doc,
output_dir,
api_name,
api_version,
api_config.get('resources', {}),
)
apis_map_file = os.path.join(base_dir, root_dir, 'apis_map.py')
package_map = collections.defaultdict(dict)
for api_name, versions_config in config['apis'].items():
for version in versions_config:
# Generated APIs all reside under same folder.
package_map[api_name][version] = root_dir.replace('/', '.')
generate.GenerateApiMap(apis_map_file, package_map, config['apis'])
# Now that everything passed, config can be updated if needed.
if changed_config:
log.warning('Updated %s', args.config)
with files.FileWriter(args.config) as stream:
ruamel.yaml.round_trip_dump(config, stream)
def _LoadConfig(config_file_name=None):
"""Loads regen config from given filename."""
config_file_name = config_file_name or os.path.join(
os.path.dirname(encoding.Decode(third_party.__file__)),
'regen_apis_config.yaml')
if not os.path.isfile(config_file_name):
raise regen_utils.ConfigFileError('{} Not found'.format(config_file_name))
with files.FileReader(config_file_name) as stream:
config = ruamel.yaml.round_trip_load(stream)
if not config or 'root_dir' not in config:
raise regen_utils.ConfigFileError(
'{} does not have format of gcloud api config file'
.format(config_file_name))
return config