def _check_folder_structure_and_get_dirs()

in src/sfctl/custom_app.py [0:0]


def _check_folder_structure_and_get_dirs(app_dir):
    """
    Check if the given path is a folder. If not, raise an exception indicating only
    SF app package folders can be compressed.

    Check if the folder given corresponds to a valid application structure. If the package
    is valid, return a list of dirs (abs path, normalized using the
    _normalize_path function) to be compressed.

    If the folder is already compressed, then return empty list.

    Example format:

    WordCountApp (this is the last segment of the app_dir path)

        o WordCountServicePkg
              Code
            •     WordCount.Service.exe
            •     Other Files
              Config
            •     Settings.xml
              ServiceManifest.xml

        o WordCountWebServicePkg
              Code
            •     WordCount.WebService.exe
            •     Other Files
              Config
            •     Settings.xml
              ServiceManifest.xml

        o    ApplicationManifest.xml

    The Code and Config folders should be compressed. These will be listed in the Application and Service manifests.

    :param app_dir: (str) An absolute path to an application package
    :return: A list of strings representing the absolute paths to directories which should
             be compressed. Return a CLIError if the provided path is not a dir
    """

    # Future optimization: don't copy already compressed packages. Just let the user know
    # and upload. This should be an uncommon case, and isn't worth the effort now

    to_compress = []

    if not os.path.isdir(app_dir):
        raise CLIError('Only Service Fabric application packages may be compressed. '
                       'The following path is not a directory: ' + app_dir)

    path_to_app_manifest = os.path.join(app_dir, 'ApplicationManifest.xml')

    # An application manifest file should exist directly under the directory passed in
    if not os.path.isfile(path_to_app_manifest):  # Casing does not matter
        raise CLIError('Application package to be compressed is missing ApplicationManifest.xml')

    # A list of the service packages. This should be the absolute path
    service_packages = []

    # Parse the application manifest to find which folders should have the service manifest.
    app_manifest_parsed = ET.parse(path_to_app_manifest).getroot()
    for child in app_manifest_parsed:
        # Use ends with, because the tags start with the xmlns
        if child.tag.endswith('ServiceManifestImport'):
            # We expect a child element that looks like:
            # <ServiceManifestRef ServiceManifestName="CalculatorServicePackage" ServiceManifestVersion="1.0"/>
            for inner_child in child:
                if inner_child.tag.endswith('ServiceManifestRef'):
                    path_to_service_package = os.path.join(app_dir, inner_child.attrib.get('ServiceManifestName'))
                    service_packages.append(path_to_service_package)

    # Go through each service package folder and search for the service manifest
    # The service manifest defines which packages are the code, config, and data packages, which
    # needs to be compressed.
    for service_package_path in service_packages:
        path_to_service_manifest = os.path.join(service_package_path, 'ServiceManifest.xml')

        # Raise exception is the expected service manifest file doesn't exist AND
        # if the service package isn't already zipped
        if not os.path.isfile(path_to_service_manifest) \
                and not os.path.isfile(path_to_service_manifest+'.sfpkg'):  # Casing does not matter
            raise CLIError('Service package to be compressed is missing ServiceManifest.xml in ' + service_package_path)

        service_manifest_parsed = ET.parse(path_to_service_manifest).getroot()

        for child in service_manifest_parsed:
            if child.tag.endswith('CodePackage') or \
                    child.tag.endswith('ConfigPackage') or child.tag.endswith('DataPackage'):

                # If the app package already compressed,
                # then mark a bool somewhere that says that this package is already
                # compressed, and expect that we just upload the entire package without copying to any location
                # In this case, we would not copy to output dir, and just upload. For partially compressed,
                # we should compress just those and throw the compressed in the output folder
                # For the case where there is no copy needed, we should print a statement letting the user know.

                folder_name = child.attrib.get("Name")
                folder_to_compress = os.path.join(service_package_path, folder_name)

                if not os.path.isdir(folder_to_compress):  # Casing does not matter
                    raise CLIError(str.format("{0} defined in {1} does not exist",
                                              folder_to_compress, path_to_service_manifest))

                to_compress.append(_normalize_path(folder_to_compress))

    return to_compress