Esempio n. 1
0
    def _gather_facts(self, modules: dict) -> bool:
        """ Gathering facts about the package - python version, docker images, valid docker image, yml parsing
        Args:
            modules(dict): Test mandatory modules to be ignore in lint check

        Returns:
            bool: Indicating if to continue further or not, if False exit Thread, Else continue.
        """
        # Looking for pkg yaml
        yml_file: Optional[Path] = self._pack_abs_dir.glob(
            [r'*.yaml', r'*.yml', r'!*unified*.yml'], flags=NEGATE)

        if not yml_file:
            logger.info(
                f"{self._pack_abs_dir} - Skipping no yaml file found {yml_file}"
            )
            self._pkg_lint_status["errors"].append(
                'Unable to find yml file in package')
            return True
        else:
            try:
                yml_file = next(yml_file)
            except StopIteration:
                return True
        # Get pack name
        self._pack_name = yml_file.stem
        log_prompt = f"{self._pack_name} - Facts"
        self._pkg_lint_status["pkg"] = yml_file.stem
        logger.info(f"{log_prompt} - Using yaml file {yml_file}")
        # Parsing pack yaml - in order to verify if check needed
        try:

            script_obj: Dict = {}
            yml_obj: Dict = YAML().load(yml_file)
            if isinstance(yml_obj, dict):
                script_obj = yml_obj.get('script', {}) if isinstance(
                    yml_obj.get('script'), dict) else yml_obj

            self._pkg_lint_status["pack_type"] = script_obj.get('type')
        except (FileNotFoundError, IOError, KeyError):
            self._pkg_lint_status["errors"].append(
                'Unable to parse package yml')
            return True
        # return no check needed if not python pack
        if self._pkg_lint_status["pack_type"] not in (TYPE_PYTHON, TYPE_PWSH):
            logger.info(
                f"{log_prompt} - Skipping due to not Python, Powershell package - Pack is"
                f" {self._pkg_lint_status['pack_type']}")
            return True
        # Docker images
        if self._facts["docker_engine"]:
            logger.info(
                f"{log_prompt} - Pulling docker images, can take up to 1-2 minutes if not exists locally "
            )
            self._facts["images"] = [[
                image, -1
            ] for image in get_all_docker_images(script_obj=script_obj)]
            # Gather environment variables for docker execution
            self._facts["env_vars"] = {
                "CI":
                os.getenv("CI", False),
                "DEMISTO_LINT_UPDATE_CERTS":
                os.getenv('DEMISTO_LINT_UPDATE_CERTS', "yes")
            }
        lint_files = set()
        # Facts for python pack
        if self._pkg_lint_status["pack_type"] == TYPE_PYTHON:
            if self._facts["docker_engine"]:
                # Getting python version from docker image - verifying if not valid docker image configured
                for image in self._facts["images"]:
                    py_num: float = get_python_version_from_image(
                        image=image[0])
                    image[1] = py_num
                    logger.info(
                        f"{self._pack_name} - Facts - {image[0]} - Python {py_num}"
                    )
                    if not self._facts["python_version"]:
                        self._facts["python_version"] = py_num
                # Checking whatever *test* exists in package
                self._facts["test"] = True if next(
                    self._pack_abs_dir.glob([r'test_*.py', r'*_test.py']),
                    None) else False
                if self._facts["test"]:
                    logger.info(f"{log_prompt} - Tests found")
                else:
                    logger.info(f"{log_prompt} - Tests not found")
                # Gather package requirements embedded test-requirements.py file
                test_requirements = self._pack_abs_dir / 'test-requirements.txt'
                if test_requirements.exists():
                    try:
                        additional_req = test_requirements.read_text(
                            encoding='utf-8').strip().split('\n')
                        self._facts["additional_requirements"].extend(
                            additional_req)
                        logger.info(
                            f"{log_prompt} - Additional package Pypi packages found - {additional_req}"
                        )
                    except (FileNotFoundError, IOError):
                        self._pkg_lint_status["errors"].append(
                            'Unable to parse test-requirements.txt in package')
            # Get lint files
            lint_files = set(
                self._pack_abs_dir.glob(["*.py", "!__init__.py", "!*.tmp"],
                                        flags=NEGATE))
        # Facts for Powershell pack
        elif self._pkg_lint_status["pack_type"] == TYPE_PWSH:
            # Get lint files
            lint_files = set(
                self._pack_abs_dir.glob([
                    "*.ps1", "!*Tests.ps1", "CommonServerPowerShell.ps1",
                    "demistomock.ps1'"
                ],
                                        flags=NEGATE))

        # Add CommonServer to the lint checks
        if 'commonserver' in self._pack_abs_dir.name.lower():
            # Powershell
            if self._pkg_lint_status["pack_type"] == TYPE_PWSH:
                self._facts["lint_files"] = [
                    Path(self._pack_abs_dir / 'CommonServerPowerShell.ps1')
                ]
            # Python
            elif self._pkg_lint_status["pack_type"] == TYPE_PYTHON:
                self._facts["lint_files"] = [
                    Path(self._pack_abs_dir / 'CommonServerPython.py')
                ]
        else:
            test_modules = {
                self._pack_abs_dir / module.name
                for module in modules.keys()
            }
            lint_files = lint_files.difference(test_modules)
            self._facts["lint_files"] = list(lint_files)
        if self._facts["lint_files"]:
            for lint_file in self._facts["lint_files"]:
                logger.info(f"{log_prompt} - Lint file {lint_file}")
        else:
            logger.info(f"{log_prompt} - Lint files not found")

        self._split_lint_files()
        return False
Esempio n. 2
0
    def run_dev_packages(self) -> int:
        return_code = 0
        # load yaml
        _, yml_path = get_yml_paths_in_dir(
            self.project_dir, Errors.no_yml_file(self.project_dir))
        if not yml_path:
            return 1
        print_v('Using yaml file: {}'.format(yml_path))
        with open(yml_path, 'r') as yml_file:
            yml_data = yaml.safe_load(yml_file)
        script_obj = yml_data
        if isinstance(script_obj.get('script'), dict):
            script_obj = script_obj.get('script')
        script_type = script_obj.get('type')
        if script_type != 'python':
            if script_type == 'powershell':
                # TODO powershell linting
                return 0

            print(
                'Script is not of type "python". Found type: {}. Nothing to do.'
                .format(script_type))
            return 0

        dockers = get_all_docker_images(script_obj)
        py_num = get_python_version(dockers[0], self.log_verbose)
        self.lock.acquire()
        print_color(
            "============ Starting process for: {} ============\n".format(
                self.project_dir), LOG_COLORS.YELLOW)
        if self.lock.locked():
            self.lock.release()
        self._setup_dev_files(py_num)
        if self.run_args['flake8']:
            result_val = self.run_flake8(py_num)
            if result_val:
                return_code = result_val

        if self.run_args['mypy']:
            result_val = self.run_mypy(py_num)
            if result_val:
                return_code = result_val

        if self.run_args['bandit']:
            result_val = self.run_bandit(py_num)
            if result_val:
                return_code = result_val

        for docker in dockers:
            for try_num in (1, 2):
                print_v("Using docker image: {}".format(docker))
                py_num = get_python_version(docker, self.log_verbose)
                try:
                    if self.run_args['tests'] or self.run_args['pylint']:
                        if py_num == 2.7:
                            requirements = self.requirements_2
                        else:
                            requirements = self.requirements_3

                        docker_image_created = self._docker_image_create(
                            docker, requirements)
                        output, status_code = self._docker_run(
                            docker_image_created)

                        self.lock.acquire()
                        print_color(
                            "\n========== Running tests/pylint for: {} ========="
                            .format(self.project_dir), LOG_COLORS.YELLOW)
                        if status_code == 1:
                            raise subprocess.CalledProcessError(*output)

                        else:
                            print(output)
                            print_color(
                                "============ Finished process for: {}  "
                                "with docker: {} ============\n".format(
                                    self.project_dir, docker),
                                LOG_COLORS.GREEN)

                        if self.lock.locked():
                            self.lock.release()

                    break  # all is good no need to retry
                except subprocess.CalledProcessError as ex:
                    if ex.output:
                        print_color(
                            "=========================== ERROR IN {}==========================="
                            "\n{}\n".format(self.project_dir, ex.output),
                            LOG_COLORS.RED)
                    else:
                        print_color(
                            "========= Test Failed on {}, Look at the error/s above ========\n"
                            .format(self.project_dir), LOG_COLORS.RED)
                        return_code = 1

                    if not self.log_verbose:
                        sys.stderr.write(
                            "Need a more detailed log? try running with the -v options as so: \n{} -v\n\n"
                            .format(" ".join(sys.argv[:])))

                    if self.lock.locked():
                        self.lock.release()

                    # circle ci docker setup sometimes fails on
                    if try_num > 1 or not ex.output or 'read: connection reset by peer' not in ex.output:
                        return 2
                    else:
                        sys.stderr.write(
                            "Retrying as failure seems to be docker communication related...\n"
                        )

                finally:
                    sys.stdout.flush()
                    sys.stderr.flush()

        return return_code
Esempio n. 3
0
    def run_dev_packages(self) -> int:
        return_code = 0
        supported_types = (TYPE_PYTHON, TYPE_PWSH)
        if self.script_type not in supported_types:
            print_warning(
                f'Script is not of types: {supported_types}. Found type: {self.script_type}. Nothing to do.'
            )
            return 0

        dockers = get_all_docker_images(self.script_obj)
        print_color(
            "============ Starting process for: {} ============\n".format(
                self.project_dir), LOG_COLORS.YELLOW)
        if self.script_type == TYPE_PYTHON:
            return_code = self.run_py_non_docker_tasks(dockers)
        if self.script_type == TYPE_PWSH:
            self._setup_dev_files_pwsh()

        for docker in dockers:
            for try_num in (1, 2):
                print_v("Using docker image: {}".format(docker))

                try:
                    if self.run_args['tests'] or self.run_args['pylint']:
                        docker_image_created = self._docker_image_create(
                            docker)
                        output, status_code = self._docker_run(
                            docker_image_created)
                        with self.lock:
                            print_color(
                                "\n========== Running tests/pylint for: {} ========="
                                .format(self.project_dir), LOG_COLORS.YELLOW)
                            if status_code == 1:
                                raise subprocess.CalledProcessError(*output)
                            else:
                                print(output)
                                print_color(
                                    "============ Finished process for: {}  "
                                    "with docker: {} ============\n".format(
                                        self.project_dir, docker),
                                    LOG_COLORS.GREEN)

                    break  # all is good no need to retry
                except subprocess.CalledProcessError as ex:
                    with self.lock:
                        if ex.output:
                            print_color(
                                "=========================== ERROR IN {}==========================="
                                "\n{}\n".format(self.project_dir, ex.output),
                                LOG_COLORS.RED)
                        else:
                            print_color(
                                "========= Test Failed on {}, Look at the error/s above ========\n"
                                .format(self.project_dir), LOG_COLORS.RED)
                            return_code = 1

                        if not get_log_verbose():
                            sys.stderr.write(
                                "Need a more detailed log? try running with the -v options as so: \n{} -v\n\n"
                                .format(" ".join(sys.argv[:])))

                        # circle ci docker setup sometimes fails on
                        if try_num > 1 or not ex.output or 'read: connection reset by peer' not in ex.output:
                            return 2
                        else:
                            sys.stderr.write(
                                "Retrying as failure seems to be docker communication related...\n"
                            )

                finally:
                    sys.stdout.flush()
                    sys.stderr.flush()

        return return_code
Esempio n. 4
0
    def extract_to_package_format(self) -> int:
        """Extracts the self.input yml file into several files according to the Demisto standard of the package format.

        Returns:
             int. status code for the operation.
        """
        try:
            output_path = self.get_output_path()
        except ValueError as ex:
            print_error(str(ex))
            return 1
        self.print_logs("Starting migration of: {} to dir: {}".format(self.input, output_path), log_color=LOG_COLORS.NATIVE)
        os.makedirs(output_path, exist_ok=True)
        base_name = os.path.basename(output_path) if not self.base_name else self.base_name
        code_file = "{}/{}".format(output_path, base_name)
        self.extract_code(code_file)
        script = self.yml_data['script']
        lang_type: str = script['type'] if self.file_type == 'integration' else self.yml_data['type']
        code_file = f"{code_file}{TYPE_TO_EXTENSION[lang_type]}"
        self.extract_image("{}/{}_image.png".format(output_path, base_name))
        self.extract_long_description("{}/{}_description.md".format(output_path, base_name))
        yaml_out = "{}/{}.yml".format(output_path, base_name)
        self.print_logs("Creating yml file: {} ...".format(yaml_out), log_color=LOG_COLORS.NATIVE)
        ryaml = YAML()
        ryaml.preserve_quotes = True
        with open(self.input, 'r') as yf:
            yaml_obj = ryaml.load(yf)
        script_obj = yaml_obj

        if self.file_type == 'integration':
            script_obj = yaml_obj['script']
            if 'image' in yaml_obj:
                del yaml_obj['image']
            if 'detaileddescription' in yaml_obj:
                del yaml_obj['detaileddescription']
        script_obj['script'] = SingleQuotedScalarString('')
        code_type = script_obj['type']
        if code_type == TYPE_PWSH and not yaml_obj.get('fromversion'):
            self.print_logs("Setting fromversion for PowerShell to: 5.5.0", log_color=LOG_COLORS.NATIVE)
            yaml_obj['fromversion'] = "5.5.0"
        with open(yaml_out, 'w') as yf:
            ryaml.dump(yaml_obj, yf)
        # check if there is a README and if found, set found_readme to True
        found_readme = False
        if self.readme:
            yml_readme = os.path.splitext(self.input)[0] + '_README.md'
            readme = output_path + '/README.md'
            if os.path.exists(yml_readme):
                found_readme = True
                self.print_logs(f"Copying {readme} to {readme}", log_color=LOG_COLORS.NATIVE)
                shutil.copy(yml_readme, readme)
            else:
                # open an empty file
                with open(readme, 'w'):
                    pass

        # Python code formatting and dev env setup
        if code_type == TYPE_PYTHON:
            if self.basic_fmt:
                self.print_logs("Running autopep8 on file: {} ...".format(code_file), log_color=LOG_COLORS.NATIVE)
                try:
                    subprocess.call(["autopep8", "-i", "--max-line-length", "130", code_file])
                except FileNotFoundError:
                    self.print_logs("autopep8 skipped! It doesn't seem you have autopep8 installed.\n"
                                    "Make sure to install it with: pip install autopep8.\n"
                                    "Then run: autopep8 -i {}".format(code_file), LOG_COLORS.YELLOW)
            if self.pipenv:
                if self.basic_fmt:
                    self.print_logs("Running isort on file: {} ...".format(code_file), LOG_COLORS.NATIVE)
                    try:
                        subprocess.call(["isort", code_file])
                    except FileNotFoundError:
                        self.print_logs("isort skipped! It doesn't seem you have isort installed.\n"
                                        "Make sure to install it with: pip install isort.\n"
                                        "Then run: isort {}".format(code_file), LOG_COLORS.YELLOW)

                self.print_logs("Detecting python version and setting up pipenv files ...", log_color=LOG_COLORS.NATIVE)
                docker = get_all_docker_images(script_obj)[0]
                py_ver = get_python_version(docker, self.config.log_verbose)
                pip_env_dir = get_pipenv_dir(py_ver, self.config.envs_dirs_base)
                self.print_logs("Copying pipenv files from: {}".format(pip_env_dir), log_color=LOG_COLORS.NATIVE)
                shutil.copy("{}/Pipfile".format(pip_env_dir), output_path)
                shutil.copy("{}/Pipfile.lock".format(pip_env_dir), output_path)
                env = os.environ.copy()
                env["PIPENV_IGNORE_VIRTUALENVS"] = "1"
                try:
                    subprocess.call(["pipenv", "install", "--dev"], cwd=output_path, env=env)
                    self.print_logs("Installing all py requirements from docker: [{}] into pipenv".format(docker),
                                    LOG_COLORS.NATIVE)
                    requirements = get_pip_requirements(docker)
                    fp = tempfile.NamedTemporaryFile(delete=False)
                    fp.write(requirements.encode('utf-8'))
                    fp.close()

                    try:
                        subprocess.check_call(["pipenv", "install", "-r", fp.name], cwd=output_path, env=env)

                    except Exception:
                        self.print_logs("Failed installing requirements in pipenv.\n "
                                        "Please try installing manually after extract ends\n", LOG_COLORS.RED)

                    os.unlink(fp.name)
                    self.print_logs("Installing flake8 for linting", log_color=LOG_COLORS.NATIVE)
                    subprocess.call(["pipenv", "install", "--dev", "flake8"], cwd=output_path, env=env)
                except FileNotFoundError as err:
                    self.print_logs("pipenv install skipped! It doesn't seem you have pipenv installed.\n"
                                    "Make sure to install it with: pip3 install pipenv.\n"
                                    f"Then run in the package dir: pipenv install --dev\n.Err: {err}", LOG_COLORS.YELLOW)
                arg_path = os.path.relpath(output_path)
                self.print_logs("\nCompleted: setting up package: {}\n".format(arg_path), LOG_COLORS.GREEN)
                next_steps: str = "Next steps: \n" \
                                  "* Install additional py packages for unit testing (if needed): cd {};" \
                                  " pipenv install <package>\n".format(arg_path) if code_type == TYPE_PYTHON else ''
                next_steps += "* Create unit tests\n" \
                              "* Check linting and unit tests by running: demisto-sdk lint -i {}\n".format(arg_path)
                next_steps += "* When ready, remove from git the old yml and/or README and add the new package:\n" \
                              "    git rm {}\n".format(self.input)
                if found_readme:
                    next_steps += "    git rm {}\n".format(os.path.splitext(self.input)[0] + '_README.md')
                next_steps += "    git add {}\n".format(arg_path)
                self.print_logs(next_steps, log_color=LOG_COLORS.NATIVE)

            else:
                self.print_logs("Skipping pipenv and requirements installation - Note: no Pipfile will be created",
                                log_color=LOG_COLORS.YELLOW)

        self.print_logs(f"Finished splitting the yml file - you can find the split results here: {output_path}",
                        log_color=LOG_COLORS.GREEN)
        return 0
Esempio n. 5
0
    def extract_to_package_format(self) -> int:
        """Extracts the self.input yml file into several files according to the Demisto standard of the package format.

        Returns:
             int. status code for the operation.
        """
        print("Starting migration of: {} to dir: {}".format(
            self.input, self.output))
        arg_path = self.output
        output_path = os.path.abspath(self.output)
        os.makedirs(output_path, exist_ok=True)
        base_name = os.path.basename(output_path)
        code_file = "{}/{}.py".format(output_path, base_name)
        self.extract_code(code_file)
        self.extract_image("{}/{}_image.png".format(output_path, base_name))
        self.extract_long_description("{}/{}_description.md".format(
            output_path, base_name))
        yaml_out = "{}/{}.yml".format(output_path, base_name)
        print("Creating yml file: {} ...".format(yaml_out))
        ryaml = YAML()
        ryaml.preserve_quotes = True
        with open(self.input, 'r') as yf:
            yaml_obj = ryaml.load(yf)
        script_obj = yaml_obj

        if self.file_type == 'integration':
            script_obj = yaml_obj['script']
            del yaml_obj['image']
            if 'detaileddescription' in yaml_obj:
                del yaml_obj['detaileddescription']

        if script_obj['type'] != 'python':
            print(
                'Script is not of type "python". Found type: {}. Nothing to do.'
                .format(script_obj['type']))
            return 1
        script_obj['script'] = SingleQuotedScalarString('')
        with open(yaml_out, 'w') as yf:
            ryaml.dump(yaml_obj, yf)
        print("Running autopep8 on file: {} ...".format(code_file))

        try:
            subprocess.call(
                ["autopep8", "-i", "--max-line-length", "130", code_file])
        except FileNotFoundError:
            print_color(
                "autopep8 skipped! It doesn't seem you have autopep8 installed.\n"
                "Make sure to install it with: pip install autopep8.\n"
                "Then run: autopep8 -i {}".format(code_file),
                LOG_COLORS.YELLOW)

        print("Detecting python version and setting up pipenv files ...")
        docker = get_all_docker_images(script_obj)[0]
        py_ver = get_python_version(docker, self.config.log_verbose)
        pip_env_dir = get_pipenv_dir(py_ver, self.config.envs_dirs_base)
        print("Copying pipenv files from: {}".format(pip_env_dir))
        shutil.copy("{}/Pipfile".format(pip_env_dir), output_path)
        shutil.copy("{}/Pipfile.lock".format(pip_env_dir), output_path)
        try:
            subprocess.call(["pipenv", "install", "--dev"], cwd=output_path)
            print(
                "Installing all py requirements from docker: [{}] into pipenv".
                format(docker))
            requirements = subprocess.check_output(
                [
                    "docker", "run", "--rm", docker, "pip", "freeze",
                    "--disable-pip-version-check"
                ],
                universal_newlines=True,
                stderr=subprocess.DEVNULL).strip()
            fp = tempfile.NamedTemporaryFile(delete=False)
            fp.write(requirements.encode('utf-8'))
            fp.close()

            try:
                subprocess.check_call(["pipenv", "install", "-r", fp.name],
                                      cwd=output_path)

            except Exception:
                print_color(
                    "Failed installing requirements in pipenv.\n "
                    "Please try installing manually after extract ends\n",
                    LOG_COLORS.RED)

            os.unlink(fp.name)
            print("Installing flake8 for linting")
            subprocess.call(["pipenv", "install", "--dev", "flake8"],
                            cwd=output_path)
        except FileNotFoundError:
            print_color(
                "pipenv install skipped! It doesn't seem you have pipenv installed.\n"
                "Make sure to install it with: pip3 install pipenv.\n"
                "Then run in the package dir: pipenv install --dev",
                LOG_COLORS.YELLOW)
        # check if there is a changelog
        yml_changelog = os.path.splitext(self.input)[0] + '_CHANGELOG.md'
        changelog = arg_path + '/CHANGELOG.md'
        if os.path.exists(yml_changelog):
            shutil.copy(yml_changelog, changelog)
        else:
            with open(changelog, 'wt', encoding='utf-8') as changelog_file:
                changelog_file.write("## [Unreleased]\n-\n")
        print_color("\nCompleted: setting up package: {}\n".format(arg_path),
                    LOG_COLORS.GREEN)
        print(
            "Next steps: \n",
            "* Install additional py packages for unit testing (if needed): cd {}; pipenv install <package>\n"
            .format(arg_path),
            "* Create unit tests\n",
            "* Check linting and unit tests by running: demisto-sdk lint -d {}\n"
            .format(arg_path),
            "* When ready rm from git the source yml and add the new package:\n",
            "    git rm {}\n".format(self.input),
            "    git add {}\n".format(arg_path),
            sep='')
        return 0