text: #!/usr/bin/python
  
  # Copyright 2017-present Open Networking Foundation
  #
  # 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.
  
  # TO RUN
  # source scripts/setup_venv.sh
  # xos-migrate [-s <service-name>] [-r ~/cord]
  # eg: xos-migrate -r ~/Sites/cord -s core -s fabric
  
  # TODO
  # - add support to specify a name to be given to the generated migration (--name parameter in django makemigrations)
  # - add support to generate empty migrations (needed for data-only migrations)
  
  import os
  import sys
  import argparse
  import yaml
  import shutil
  from xosgenx.generator import XOSProcessor, XOSProcessorArgs
  from xosconfig import Config
  from multistructlog import create_logger
  
  
  def get_abs_path(dir_):
      if os.path.isabs(dir_):
          return os.path.realpath(dir_)
      if dir_[0] == "~" and not os.path.exists(dir_):
          dir_ = os.path.expanduser(dir_)
          return os.path.abspath(dir_)
      return os.path.dirname(os.path.realpath(__file__)) + "/" + dir_
  
  
  def print_banner(root):
      log.info(r"---------------------------------------------------------------")
      log.info(r"                                    _                  __      ")
      log.info(r"   _  ______  _____      ____ ___  (_)___ __________ _/ /____  ")
      log.info(r"  | |/_/ __ \/ ___/_____/ __ `__ \/ / __ `/ ___/ __ `/ __/ _ \ ")
      log.info(r" _>  </ /_/ (__  )_____/ / / / / / / /_/ / /  / /_/ / /_/  __/ ")
      log.info(r"/_/|_|\____/____/     /_/ /_/ /_/_/\__, /_/   \__,_/\__/\___/  ")
      log.info(r"                                  /____/                       ")
      log.info(r"---------------------------------------------------------------")
      log.debug("CORD repo root", root=root)
      log.debug("Storing logs in: %s" % os.environ["LOG_FILE"])
      log.debug(r"---------------------------------------------------------------")
  
  
  def generate_core_models(core_dir):
      core_xproto = os.path.join(core_dir, "core.xproto")
  
      args = XOSProcessorArgs(
          output=core_dir,
          target="django.xtarget",
          dest_extension="py",
          write_to_file="model",
          files=[core_xproto],
      )
      XOSProcessor.process(args)
  
      security_args = XOSProcessorArgs(
          output=core_dir,
          target="django-security.xtarget",
          dest_file="security.py",
          write_to_file="single",
          files=[core_xproto],
      )
  
      XOSProcessor.process(security_args)
  
      init_args = XOSProcessorArgs(
          output=core_dir,
          target="init.xtarget",
          dest_file="__init__.py",
          write_to_file="single",
          files=[core_xproto],
      )
      XOSProcessor.process(init_args)
  
  
  def find_xproto_in_folder(path):
      """
      Recursively iterate a folder tree to look for any xProto file.
      We use this function in case that the name of the xProto is different from the name of the folder (eg: olt-service)
      :param path: the root folder to start the search
      :return: [string]
      """
      xprotos = []
      for fn in os.listdir(path):
          # skip hidden files and folders. plus other useless things
          if fn.startswith(".") or fn == "venv-xos" or fn == "htmlcov":
              continue
          full_path = os.path.join(path, fn)
          if fn.endswith(".xproto"):
              xprotos.append(full_path)
          elif os.path.isdir(full_path):
              xprotos = xprotos + find_xproto_in_folder(full_path)
      return xprotos
  
  
  def find_decls_models(path):
      """
      Recursively iterate a folder tree to look for any models.py file.
      This files contain the base model for _decl generated models.
      :param path: the root folder to start the search
      :return: [string]
      """
      decls = []
      for fn in os.listdir(path):
          # skip hidden files and folders. plus other useless things
          if fn.startswith(".") or fn == "venv-xos" or fn == "htmlcov":
              continue
          full_path = os.path.join(path, fn)
          if fn == "models.py":
              decls.append(full_path)
          elif os.path.isdir(full_path):
              decls = decls + find_decls_models(full_path)
      return decls
  
  
  def get_service_name_from_config(path):
      """
      Given a service folder look for the config.yaml file and find the name
      :param path: the root folder to start the search
      :return: string
      """
      config = os.path.join(path, "xos/synchronizer/config.yaml")
      if not os.path.isfile(config):
          raise Exception("Config file not found at: %s" % config)
  
      cfg_file = open(config)
      cfg = yaml.load(cfg_file)
      return cfg["name"]
  
  
  def generate_service_models(service_dir, service_dest_dir, service_name):
      """
      Generate the django code starting from xProto for a given service.
      :param service_dir: string (path to the folder)
      :param service_name: string (name of the service)
      :return: void
      """
      xprotos = find_xproto_in_folder(service_dir)
      decls = find_decls_models(service_dir)
      log.debug("Generating models for %s from files %s" % (service_name, ", ".join(xprotos)))
      out_dir = os.path.join(service_dest_dir, service_name)
      if not os.path.isdir(out_dir):
          os.mkdir(out_dir)
  
      args = XOSProcessorArgs(
          output=out_dir,
          files=xprotos,
          target="service.xtarget",
          write_to_file="target",
      )
      XOSProcessor.process(args)
  
      security_args = XOSProcessorArgs(
          output=out_dir,
          target="django-security.xtarget",
          dest_file="security.py",
          write_to_file="single",
          files=xprotos,
      )
  
      XOSProcessor.process(security_args)
  
      init_py_filename = os.path.join(out_dir, "__init__.py")
      if not os.path.exists(init_py_filename):
          open(init_py_filename, "w").write("# created by dynamicbuild")
  
      # copy over models.py files from the service
      if len(decls) > 0:
          for file in decls:
              fn = os.path.basename(file)
              src_fn = file
              dest_fn = os.path.join(out_dir, fn)
              log.debug("Copying models.py from %s to %s" % (src_fn, dest_fn))
              shutil.copyfile(src_fn, dest_fn)
  
      # copy existing migrations from the service, otherwise they won't be incremental
      src_dir = os.path.join(service_dir, "xos", "synchronizer", "migrations")
      if os.path.isdir(src_dir):
          dest_dir = os.path.join(out_dir, "migrations")
          if os.path.isdir(dest_dir):
              shutil.rmtree(dest_dir)  # empty the folder, we'll copy everything again
          shutil.copytree(src_dir, dest_dir)
  
  
  def copy_service_migrations(service_dir, service_dest_dir, service_name):
      """
      Once the migrations are generated, copy them in the correct location
      :param service_dir: string (path to the folder)
      :param service_name: string (name of the service)
      :return: void
      """
      log.debug("Copying %s migrations to %s" % (service_name, service_dir))
      migration_dir = os.path.join(service_dest_dir, service_name, "migrations")
      dest_dir = os.path.join(service_dir, "xos", "synchronizer", "migrations")
      if os.path.isdir(dest_dir):
          shutil.rmtree(dest_dir)  # empty the folder, we'll copy everything again
      shutil.copytree(migration_dir, dest_dir)
      # clean after the tool, generated migrations has been moved in the service repo
      shutil.rmtree(get_abs_path(os.path.join(migration_dir, "../")))
  
  
  def monkey_patch_migration_template():
      import django
      django.setup()
  
      import django.db.migrations.writer as dj
      dj.MIGRATION_TEMPLATE = """\
  # Copyright 2017-present Open Networking Foundation
  #
  # 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.
  
  # -*- coding: utf-8 -*-
  # Generated by Django %(version)s on %(timestamp)s
  from __future__ import unicode_literals
  
  %(imports)s
  
  class Migration(migrations.Migration):
  %(replaces_str)s%(initial_str)s
      dependencies = [
  %(dependencies)s\
      ]
  
      operations = [
  %(operations)s\
      ]
  """
  
  
  def configure_logging(verbose):
      global log
      # INITIALIZING LOGGER
      Config.init()
  
      cfg = Config().get("logging")
      if verbose:
          cfg["handlers"]["console"]["level"] = "DEBUG"
  
      log = create_logger(cfg)
  
  
  # SETTING ENV
  os.environ["LOG_FILE"] = get_abs_path("django.log")
  os.environ["XOS_CONFIG_SCHEMA"] = get_abs_path("migration_cfg_schema.yaml")
  os.environ["XOS_CONFIG_FILE"] = get_abs_path("migration_cfg.yaml")
  os.environ["MIGRATIONS"] = "true"
  # this is populated in case we generate migrations for services and it's used in settings.py
  os.environ["INSTALLED_APPS"] = ""
  
  # PARAMS
  parser = argparse.ArgumentParser(description="XOS Migrations")
  required = parser.add_argument_group("required arguments")
  
  required.add_argument(
      "-s",
      "--service",
      action="append",
      required=True,
      dest="service_names",
      help="The name of the folder containing the service in cord/orchestration/xos_services"
  )
  
  parser.add_argument(
      "-r",
      "--repo",
      default=get_abs_path("~/cord"),
      dest="repo_root",
      help="The location of the folder containing the CORD repo root (default to ~/cord)"
  )
  
  parser.add_argument(
      "--check",
      default=False,
      action="store_true",
      dest="check",
      help="Check if the migrations are generated for a given service. Does not apply any change."
  )
  
  parser.add_argument(
      "-v",
      "--verbose",
      help="increase log verbosity",
      dest="verbose",
      action="store_true"
  )
  
  
  def run():
      # cleaning up from possible incorrect states
      if "INSTALLED_APPS" in os.environ:
          del os.environ["INSTALLED_APPS"]
  
      args = parser.parse_args()
  
      configure_logging(args.verbose)
  
      print_banner(args.repo_root)
  
      # find absolute path to the code
      xos_path = get_abs_path(os.path.join(args.repo_root, "orchestration/xos/xos/"))
      django_path = get_abs_path(os.path.join(xos_path, "manage.py"))
      core_dir = get_abs_path(os.path.join(xos_path, "core/models/"))
      service_base_dir = get_abs_path(os.path.join(xos_path, "../../xos_services/"))
      service_dest_dir = get_abs_path(os.path.join(xos_path, "services/"))
  
      # we need to append the xos folder to sys.path
      original_sys_path = sys.path
      sys.path.append(xos_path)
  
      log.info("Services: %s" % ", ".join(args.service_names))
  
      django_cli_args = ['xos-migrate.py', "makemigrations"]
  
      # generate the code for each service and create a list of parameters to pass to django
      app_list = []
      for service in args.service_names:
          # NOTE we need core models to be there as all the services depend on them
          generate_core_models(core_dir)
          if service == "core":
              django_cli_args.append("core")
          else:
              service_dir = os.path.join(service_base_dir, service)
              service_name = get_service_name_from_config(service_dir)
              generate_service_models(service_dir, service_dest_dir, service_name)
              app_list.append("services.%s" % service_name)
  
              django_cli_args.append(service_name)
  
      if len(app_list) > 0:
          os.environ["INSTALLED_APPS"] = ",".join(app_list)
  
      os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
  
      monkey_patch_migration_template()
  
      if args.check:
          django_cli_args.append("--check")
          django_cli_args.append("--dry-run")
  
      from django.core.management import execute_from_command_line
  
      try:
          log.debug("Django CLI Args", args=django_cli_args)
          execute_from_command_line(django_cli_args)
          returncode = 0
      except SystemExit as e:
          returncode = e.message
  
      if returncode != 0:
          if args.check:
              log.error("Migrations are not up to date with the service changes!")
          else:
              log.error("An error occurred")
          sys.exit(returncode)
  
      # copying migrations back to the service
      for service in args.service_names:
          if service == "core":
              # we don't need to copy migrations for the core
              continue
          else:
              service_dir = os.path.join(service_base_dir, service)
              service_name = get_service_name_from_config(service_dir)
              copy_service_migrations(service_dir, service_dest_dir, service_name)
  
      # restore orginal sys.path
      sys.path = original_sys_path
  