Esempio n. 1
0
    def __init__(self, elt_context: ELTContext):
        self.context = elt_context

        self.project_settings_service = ProjectSettingsService(
            self.context.project,
            config_service=self.context.plugins_service.config_service,
        )
class UIAvailableWorker(threading.Thread):
    def __init__(self, project, open_browser=False):
        super().__init__()

        self.project = project
        self.open_browser = open_browser

        self.settings_service = ProjectSettingsService(self.project)

        self._terminate = False

    def run(self):
        bind_port = self.settings_service.get("ui.bind_port")
        url = f"http://localhost:{bind_port}"

        server_name = self.settings_service.get("ui.server_name")
        headers = {"Host": server_name}

        while not self._terminate:
            try:
                response = requests.get(url, headers=headers)
                if response.status_code == 200:
                    click.secho(f"Meltano UI is now available at {url}",
                                fg="green")
                    if self.open_browser:
                        webbrowser.open(url)
                    self._terminate = True
            except:
                pass

            time.sleep(2)

    def stop(self):
        self._terminate = True
Esempio n. 3
0
    def __init__(
        self,
        project: Project,
        plugin: ProjectPlugin,
        *args,
        plugins_service: ProjectPluginsService = None,
        **kwargs,
    ):
        super().__init__(project, *args, **kwargs)

        self.plugin = plugin
        self.plugins_service = plugins_service or ProjectPluginsService(self.project)

        project_settings_service = ProjectSettingsService(
            self.project, config_service=self.plugins_service.config_service
        )

        self.env_override = {
            **project_settings_service.env,
            **project_settings_service.as_env(),
            **self.env_override,
            **self.plugin.info_env,
        }

        self._inherited_settings_service = None
Esempio n. 4
0
    def test_ui_setup(self, project, cli_runner, monkeypatch):
        monkeypatch.setenv("MELTANO_UI_SECRET_KEY", "existing_secret_key")

        with mock.patch("meltano.cli.ui.secrets.token_hex",
                        return_value="fake_secret"):
            result = cli_runner.invoke(cli,
                                       ["ui", "setup", "meltano.example.com"])

        assert_cli_runner(result)

        assert (
            "Setting 'ui.secret_key' has already been set in the environment"
            in result.stdout)

        settings_service = ProjectSettingsService(project)

        assert settings_service.get_with_source("ui.server_name") == (
            "meltano.example.com",
            SettingValueStore.DOTENV,
        )
        assert settings_service.get_with_source("ui.secret_key") == (
            "existing_secret_key",
            SettingValueStore.ENV,
        )
        assert settings_service.get_with_source("ui.password_salt") == (
            "fake_secret",
            SettingValueStore.DOTENV,
        )
Esempio n. 5
0
    def decorated(*args, **kwargs):
        project = Project.find()
        settings_service = ProjectSettingsService(project)

        if settings_service.get("ui.readonly"):
            return (
                jsonify({
                    "error": True,
                    "code": "Meltano UI is running in read-only mode"
                }),
                HTTP_READONLY_CODE,
            )

        if settings_service.get(
                "ui.anonymous_readonly") and current_user.is_anonymous:
            return (
                jsonify({
                    "error":
                    True,
                    "code":
                    "Meltano UI is running in read-only mode until you sign in",
                }),
                HTTP_READONLY_CODE,
            )

        return f(*args, **kwargs)
    def __init__(self, project, open_browser=False):
        super().__init__()

        self.project = project
        self.open_browser = open_browser

        self.settings_service = ProjectSettingsService(self.project)

        self._terminate = False
Esempio n. 7
0
    def __init__(self,
                 project,
                 tracking_id: str = None,
                 request_timeout: float = None):
        self.project = project
        self.settings_service = ProjectSettingsService(self.project)

        self.tracking_id = tracking_id or self.settings_service.get(
            "tracking_ids.cli")
        self.request_timeout = request_timeout or REQUEST_TIMEOUT

        self.send_anonymous_usage_stats = self.settings_service.get(
            "send_anonymous_usage_stats")
        self.project_id = self.load_project_id()
        self.client_id = self.load_client_id()
Esempio n. 8
0
def cli(ctx, log_level, verbose):
    """
    Get help at https://www.meltano.com/docs/command-line-interface.html
    """
    if log_level:
        ProjectSettingsService.config_override["cli.log_level"] = log_level

    ctx.ensure_object(dict)
    ctx.obj["verbosity"] = verbose

    try:
        project = Project.find()
        setup_logging(project)

        readonly = ProjectSettingsService(project).get("project_readonly")
        if readonly:
            project.readonly = True

        if project.readonly:
            logger.debug("Project is read-only.")

        ctx.obj["project"] = project
    except ProjectNotFound as err:
        ctx.obj["project"] = None
    except IncompatibleVersionError as err:
        click.secho(
            "This Meltano project is incompatible with this version of `meltano`.",
            fg="yellow",
        )
        click.echo(
            "For more details, visit http://meltano.com/docs/installation.html#upgrading-meltano-version"
        )
        sys.exit(3)
Esempio n. 9
0
class ProjectSettings:
    settings_map = {
        "SERVER_NAME": "ui.server_name",
        "SESSION_COOKIE_DOMAIN": "ui.session_cookie_domain",
        "SESSION_COOKIE_SECURE": "ui.session_cookie_secure",
        "SECRET_KEY": "ui.secret_key",
        # Flask-Security
        "SECURITY_PASSWORD_SALT": "ui.password_salt",
        # Flask-SQLAlchemy
        "SQLALCHEMY_DATABASE_URI": "database_uri",
        # Flask-Authlib
        "GITLAB_CLIENT_ID": "oauth.gitlab.client_id",
        "GITLAB_CLIENT_SECRET": "oauth.gitlab.client_secret",
        # Flask-Mail
        "MAIL_SERVER": "mail.server",
        "MAIL_PORT": "mail.port",
        "MAIL_DEFAULT_SENDER": "mail.default_sender",
        "MAIL_USE_TLS": "mail.use_tls",
        "MAIL_USERNAME": "******",
        "MAIL_PASSWORD": "******",
        "MAIL_DEBUG": "mail.debug",
    }

    def __init__(self, project: Project):
        self.settings_service = ProjectSettingsService(project)

    def as_dict(self):
        return {
            config_key: self.settings_service.get(setting_name)
            for config_key, setting_name in self.settings_map.items()
        }
Esempio n. 10
0
    def test_activate_project_readonly_dotenv(self, project, cli_runner, pushd,
                                              monkeypatch):
        ProjectSettingsService(project).set("project_readonly", True)

        assert Project._default is None

        pushd(project.root)
        cli_runner.invoke(cli, ["discover"])

        assert Project._default.readonly == True
Esempio n. 11
0
def identity():
    project = Project.find()
    settings_service = ProjectSettingsService(project)

    if current_user.is_anonymous:
        return jsonify({
            "username":
            "******",
            "anonymous":
            True,
            "can_sign_in":
            settings_service.get("ui.authentication"),
        })

    return jsonify({
        "username": current_user.username,
        "anonymous": False,
        "can_sign_in": False
    })
Esempio n. 12
0
def setup_security(app, project):
    options = {
        "login_form": MeltanoLoginForm,
        "register_form": MeltanoRegisterFrom,
        "confirm_register_form": MeltanoConfirmRegisterForm,
    }

    settings_service = ProjectSettingsService(project)
    if not settings_service.get("ui.authentication"):
        # the FreeUser is free to do everything and has all
        # roles and permissions automatically.
        options["anonymous_user"] = FreeUser
    else:
        # Use Flask's built-in AnonymousUser which is not deemed to be authenticated
        # and has no roles
        pass

    security.init_app(app, users, **options)
    security.unauthorized_handler(unauthorized_callback)
    user_logged_in.connect_via(app)(_user_logged_in_hook)
    identity_loaded.connect_via(app)(_identity_loaded_hook)
Esempio n. 13
0
def ensure_secure_setup(project):
    settings_service = ProjectSettingsService(project)

    if not settings_service.get("ui.authentication"):
        return

    facts = []
    if (
        settings_service.get("ui.server_name") is None
        and settings_service.get("ui.session_cookie_domain") is None
    ):
        facts.append(
            f"- Neither the 'ui.server_name' or 'ui.session_cookie_domain' setting has been set"
        )

    secure_settings = ["ui.secret_key", "ui.password_salt"]
    for setting_name in secure_settings:
        value, source = settings_service.get_with_source(setting_name)
        if source is SettingValueStore.DEFAULT:
            facts.append(
                f"- The '{setting_name}' setting has not been changed from the default test value"
            )

    if facts:
        click.secho(
            "Authentication is enabled, but your configuration is currently insecure:",
            fg="red",
        )
        for fact in facts:
            click.echo(fact)
        click.echo(
            "For more information about these settings and how to set them, visit https://www.meltano.com/docs/settings.html#ui-authentication"
        )
        click.echo()
Esempio n. 14
0
def passes_authentication_checks():
    project = Project.find()
    settings_service = ProjectSettingsService(project)

    if not settings_service.get("ui.authentication"):
        logging.debug(f"Authentication not required because it's disabled")
        return True

    if current_user.is_authenticated:
        logging.debug(f"Authenticated as '{current_user.username}'")
        return True

    if settings_service.get(
            "ui.anonymous_readonly") and current_user.is_anonymous:
        # The `@roles_required("admin")` and `@block_if_readonly` checks
        # will take care of enforcing authentication as appropriate
        logging.debug(
            f"Authentication not required because anonymous users have read-only access"
        )
        return True

    return False
Esempio n. 15
0
def config(ctx, project, plugin_type, plugin_name, format, extras):
    plugin_type = PluginType.from_cli_argument(
        plugin_type) if plugin_type else None

    plugins_service = ProjectPluginsService(project)

    try:
        plugin = plugins_service.find_plugin(plugin_name,
                                             plugin_type=plugin_type,
                                             configurable=True)
    except PluginNotFoundError:
        if plugin_name == "meltano":
            plugin = None
        else:
            raise

    _, Session = project_engine(project)
    session = Session()
    try:
        if plugin:
            settings = PluginSettingsService(project,
                                             plugin,
                                             plugins_service=plugins_service)
        else:
            settings = ProjectSettingsService(
                project, config_service=plugins_service.config_service)

        ctx.obj["settings"] = settings
        ctx.obj["session"] = session

        if ctx.invoked_subcommand is None:
            if format == "json":
                process = extras is not True
                config = settings.as_dict(extras=extras,
                                          process=process,
                                          session=session)
                print(json.dumps(config, indent=2))
            elif format == "env":
                env = settings.as_env(extras=extras, session=session)

                with tempfile.NamedTemporaryFile() as temp_dotenv:
                    path = temp_dotenv.name
                    for key, value in env.items():
                        dotenv.set_key(path, key, value)

                    dotenv_content = Path(temp_dotenv.name).read_text()

                print(dotenv_content, end="")
    finally:
        session.close()
Esempio n. 16
0
def setup(ctx, server_name, **flags):
    """
    Generates and stores server name and secrets.
    """
    project = ctx.obj["project"]
    settings_service = ProjectSettingsService(project)

    def set_setting_env(setting_name, value):
        settings_service.set(setting_name, value, store=SettingValueStore.DOTENV)

    set_setting_env("ui.server_name", server_name)

    ui_cfg_path = project.root_dir("ui.cfg")
    if ui_cfg_path.exists():
        raise CliError(
            f"Found existing secrets in file '{ui_cfg_path}'. Please delete this file and rerun this command to regenerate the secrets."
        )

    generate_secret = lambda: secrets.token_hex(int(flags["bits"] / 8))  # in bytes

    secret_settings = ["ui.secret_key", "ui.password_salt"]
    for setting_name in secret_settings:
        value, source = settings_service.get_with_source(setting_name)
        if source is not SettingValueStore.DEFAULT:
            click.echo(
                f"Setting '{setting_name}' has already been set in {source.label}. Please unset it manually and rerun this command to regenerate this secret."
            )
        else:
            set_setting_env(setting_name, generate_secret())

    click.echo(
        "The server name and generated secrets have been stored in your project's `.env` file."
    )
    click.echo(
        "In production, you will likely want to move these settings to actual environment variables, since `.env` is in `.gitignore` by default."
    )
Esempio n. 17
0
class SingerRunner(Runner):
    def __init__(self, elt_context: ELTContext):
        self.context = elt_context

        self.project_settings_service = ProjectSettingsService(
            self.context.project,
            config_service=self.context.plugins_service.config_service,
        )

    def stop(self, process, **wait_args):
        while True:
            try:
                code = process.wait(**wait_args)
                logging.debug(f"{process} exited with {code}")
                return code
            except subprocess.TimeoutExpired:
                process.kill()
                logging.error(f"{process} was killed.")

    async def invoke(  # noqa: WPS217, WPS210, WPS211, WPS213, WPS231
        self,
        tap: PluginInvoker,
        target: PluginInvoker,
        extractor_log=None,
        loader_log=None,
        extractor_out=None,
        loader_out=None,
    ):
        """Invoke tap and target together."""
        extractor_log = extractor_log or sys.stderr
        loader_log = loader_log or sys.stderr

        # The StreamReader line length limit also acts as half the buffer size,
        # which cannot be set directly:
        # - https://github.com/python/cpython/blob/v3.8.7/Lib/asyncio/streams.py#L395-L396
        # - https://github.com/python/cpython/blob/v3.8.7/Lib/asyncio/streams.py#L482
        stream_buffer_size = self.project_settings_service.get("elt.buffer_size")
        line_length_limit = stream_buffer_size // 2

        # Start tap
        try:
            p_tap = await tap.invoke_async(
                limit=line_length_limit,
                stdout=asyncio.subprocess.PIPE,  # Singer messages
                stderr=asyncio.subprocess.PIPE,  # Log
            )
        except Exception as err:
            raise RunnerError(f"Cannot start extractor: {err}") from err

        # Start target
        try:
            p_target = await target.invoke_async(
                limit=line_length_limit,
                stdin=asyncio.subprocess.PIPE,  # Singer messages
                stdout=asyncio.subprocess.PIPE,  # Singer state
                stderr=asyncio.subprocess.PIPE,  # Log
            )
        except Exception as err:
            raise RunnerError(f"Cannot start loader: {err}") from err

        # Process tap output
        tap_outputs = [p_target.stdin]
        if extractor_out:
            tap_outputs.insert(0, extractor_out)

        tap_stdout_future = asyncio.ensure_future(
            capture_subprocess_output(p_tap.stdout, *tap_outputs)
        )
        tap_stderr_future = asyncio.ensure_future(
            capture_subprocess_output(p_tap.stderr, extractor_log)
        )

        # Process target output
        target_outputs = [self.bookmark_writer()]
        if loader_out:
            target_outputs.insert(0, loader_out)

        target_stdout_future = asyncio.ensure_future(
            capture_subprocess_output(p_target.stdout, *target_outputs)
        )
        target_stderr_future = asyncio.ensure_future(
            capture_subprocess_output(p_target.stderr, loader_log)
        )

        # Wait for tap or target to complete, or for one of the output handlers to raise an exception.
        tap_process_future = asyncio.ensure_future(p_tap.wait())
        target_process_future = asyncio.ensure_future(p_target.wait())
        output_exception_future = asyncio.ensure_future(
            asyncio.wait(
                [
                    tap_stdout_future,
                    tap_stderr_future,
                    target_stdout_future,
                    target_stderr_future,
                ],
                return_when=asyncio.FIRST_EXCEPTION,
            ),
        )

        done, _ = await asyncio.wait(
            [tap_process_future, target_process_future, output_exception_future],
            return_when=asyncio.FIRST_COMPLETED,
        )

        # If `output_exception_future` completes first, one of the output handlers raised an exception or all completed successfully.
        if output_exception_future in done:
            output_futures_done, _ = output_exception_future.result()
            output_futures_failed = [
                future
                for future in output_futures_done
                if future.exception() is not None
            ]

            if output_futures_failed:
                # If any output handler raised an exception, re-raise it.

                # Special behavior for the tap stdout handler raising a line length limit error.
                if tap_stdout_future in output_futures_failed:
                    self._handle_tap_line_length_limit_error(
                        tap_stdout_future.exception(),
                        line_length_limit=line_length_limit,
                        stream_buffer_size=stream_buffer_size,
                    )

                failed_future = output_futures_failed.pop()
                raise failed_future.exception()
            else:
                # If all of the output handlers completed without raising an exception,
                # we still need to wait for the tap or target to complete.
                done, _ = await asyncio.wait(
                    [tap_process_future, target_process_future],
                    return_when=asyncio.FIRST_COMPLETED,
                )

        if target_process_future in done:
            target_code = target_process_future.result()

            if tap_process_future in done:
                tap_code = tap_process_future.result()
            else:
                # If the target completes before the tap, it failed before processing all tap output

                # Kill tap and cancel output processing since there's no more target to forward messages to
                p_tap.kill()
                tap_stdout_future.cancel()
                tap_stderr_future.cancel()

                # Pretend the tap finished successfully since it didn't itself fail
                tap_code = 0

            # Wait for all buffered target output to be processed
            await asyncio.wait([target_stdout_future, target_stderr_future])
        else:  # if tap_process_future in done:
            # If the tap completes before the target, the target should have a chance to process all tap output
            tap_code = tap_process_future.result()

            # Wait for all buffered tap output to be processed
            await asyncio.wait([tap_stdout_future, tap_stderr_future])

            # Close target stdin so process can complete naturally
            p_target.stdin.close()

            # Wait for all buffered target output to be processed
            await asyncio.wait([target_stdout_future, target_stderr_future])

            # Wait for target to complete
            target_code = await target_process_future

        if tap_code and target_code:
            raise RunnerError(
                "Extractor and loader failed",
                {PluginType.EXTRACTORS: tap_code, PluginType.LOADERS: target_code},
            )
        elif tap_code:
            raise RunnerError("Extractor failed", {PluginType.EXTRACTORS: tap_code})
        elif target_code:
            raise RunnerError("Loader failed", {PluginType.LOADERS: target_code})

    def bookmark_writer(self):
        incomplete_state = self.context.full_refresh and self.context.select_filter
        payload_flag = Payload.INCOMPLETE_STATE if incomplete_state else Payload.STATE
        return BookmarkWriter(
            self.context.job, self.context.session, payload_flag=payload_flag
        )

    def dry_run(self, tap: PluginInvoker, target: PluginInvoker):
        logging.info("Dry run:")
        logging.info(f"\textractor: {tap.plugin.name} at '{tap.exec_path()}'")
        logging.info(f"\tloader: {target.plugin.name} at '{target.exec_path()}'")

    async def run(
        self, extractor_log=None, loader_log=None, extractor_out=None, loader_out=None
    ):
        tap = self.context.extractor_invoker()
        target = self.context.loader_invoker()

        if self.context.dry_run:
            return self.dry_run(tap, target)

        with tap.prepared(self.context.session), target.prepared(self.context.session):
            await self.invoke(
                tap,
                target,
                extractor_log=extractor_log,
                loader_log=loader_log,
                extractor_out=extractor_out,
                loader_out=loader_out,
            )

    def _handle_tap_line_length_limit_error(
        self,
        exception,
        line_length_limit,
        stream_buffer_size,
    ):
        # StreamReader.readline can raise a ValueError wrapping a LimitOverrunError:
        # https://github.com/python/cpython/blob/v3.8.7/Lib/asyncio/streams.py#L549
        if not isinstance(exception, ValueError):
            return

        exception = exception.__context__  # noqa: WPS609
        if not isinstance(exception, asyncio.LimitOverrunError):
            return

        logging.error(
            f"The extractor generated a message exceeding the message size limit of {human_size(line_length_limit)} (half the buffer size of {human_size(stream_buffer_size)})."
        )
        logging.error(
            "To let this message be processed, increase the 'elt.buffer_size' setting to at least double the size of the largest expected message, and try again."
        )
        logging.error(
            "To learn more, visit https://www.meltano.com/docs/settings.html#elt-buffer-size"
        )
        raise RunnerError("Output line length limit exceeded") from exception
Esempio n. 18
0
class GoogleAnalyticsTracker:
    def __init__(self,
                 project,
                 tracking_id: str = None,
                 request_timeout: float = None):
        self.project = project
        self.settings_service = ProjectSettingsService(self.project)

        self.tracking_id = tracking_id or self.settings_service.get(
            "tracking_ids.cli")
        self.request_timeout = request_timeout or REQUEST_TIMEOUT

        self.send_anonymous_usage_stats = self.settings_service.get(
            "send_anonymous_usage_stats")
        self.project_id = self.load_project_id()
        self.client_id = self.load_client_id()

    def load_project_id(self) -> uuid.UUID:
        """
        Fetch the project_id from the project config file.

        If it is not found (e.g. first time run), generate a valid uuid4 and
        store it in the project config file.
        """
        try:
            project_id_str = self.settings_service.get("project_id")
            project_id = uuid.UUID(project_id_str or "", version=4)
        except ValueError:
            project_id = uuid.uuid4()

            if self.send_anonymous_usage_stats:
                # If we are set to track Anonymous Usage stats, also store
                #  the generated project_id back to the project config file
                #  so that it persists between meltano runs.
                self.settings_service.set("project_id", str(project_id))

        return project_id

    def load_client_id(self) -> uuid.UUID:
        """
        Fetch the client_id from the non-versioned analytics.json.

        If it is not found (e.g. first time run), generate a valid uuid4 and
        store it in analytics.json.
        """
        file_path = self.project.meltano_dir().joinpath("analytics.json")
        try:
            with open(file_path) as file:
                file_data = json.load(file)
            client_id_str = file_data["client_id"]
            client_id = uuid.UUID(client_id_str, version=4)
        except FileNotFoundError:
            client_id = uuid.uuid4()

            if self.send_anonymous_usage_stats:
                # If we are set to track Anonymous Usage stats, also store
                #  the generated client_id in a non-versioned analytics.json file
                #  so that it persists between meltano runs.
                with open(file_path, "w") as file:
                    data = {"client_id": str(client_id)}
                    json.dump(data, file)

        return client_id

    def event(self, category: str, action: str) -> Dict:
        """Constract a GA event with all the required parameters."""
        event = {
            "v": "1",
            "tid": self.tracking_id,
            "cid": self.client_id,
            "ds": "meltano cli",
            "t": "event",
            "ec": category,
            "ea": action,
            "el": self.project_id,
            "cd1": self.project_id,  # maps to the custom dimension 1 of the UI
        }
        return event

    def track_data(self, data: Dict, debug: bool = False) -> None:
        """Send usage statistics back to Google Analytics."""
        if self.send_anonymous_usage_stats == False:
            # Only send anonymous usage stats if you have explicit permission
            return

        if debug:
            tracking_uri = DEBUG_MEASUREMENT_PROTOCOL_URI
        else:
            tracking_uri = MEASUREMENT_PROTOCOL_URI

        try:
            r = requests.post(tracking_uri,
                              data=data,
                              timeout=self.request_timeout)

            if debug:
                logging.debug("GoogleAnalyticsTracker.track_data:")
                logging.debug(data)
                logging.debug("Response:")
                logging.debug(f"status_code: {r.status_code}")
                logging.debug(r.text)
        except requests.exceptions.Timeout:
            logging.debug(
                "GoogleAnalyticsTracker.track_data: Request Timed Out")
        except requests.exceptions.ConnectionError as e:
            logging.debug("GoogleAnalyticsTracker.track_data: ConnectionError")
            logging.debug(e)
        except requests.exceptions.RequestException as e:
            logging.debug(
                "GoogleAnalyticsTracker.track_data: RequestException")
            logging.debug(e)

    def track_event(self,
                    category: str,
                    action: str,
                    debug: bool = False) -> None:
        event = self.event(category, action)
        self.track_data(event, debug)

    def track_meltano_init(self,
                           project_name: str,
                           debug: bool = False) -> None:
        event = self.track_event(category="meltano init",
                                 action=f"meltano init {project_name}",
                                 debug=debug)

    def track_meltano_add(self,
                          plugin_type: str,
                          plugin_name: str,
                          debug: bool = False) -> None:
        event = self.track_event(
            category=f"meltano add {plugin_type}",
            action=f"meltano add {plugin_type} {plugin_name}",
            debug=debug,
        )

    def track_meltano_discover(self,
                               plugin_type: str,
                               debug: bool = False) -> None:
        event = self.track_event(
            category="meltano discover",
            action=f"meltano discover {plugin_type}",
            debug=debug,
        )

    def track_meltano_elt(self,
                          extractor: str,
                          loader: str,
                          transform: str,
                          debug: bool = False) -> None:
        event = self.track_event(
            category="meltano elt",
            action=f"meltano elt {extractor} {loader} --transform {transform}",
            debug=debug,
        )

    def track_meltano_install(self, debug: bool = False) -> None:
        event = self.track_event(category="meltano install",
                                 action="meltano install",
                                 debug=debug)

    def track_meltano_invoke(self,
                             plugin_name: str,
                             plugin_args: str,
                             debug: bool = False) -> None:
        event = self.track_event(
            category="meltano invoke",
            action=f"meltano invoke {plugin_name} {plugin_args}",
            debug=debug,
        )

    def track_meltano_schedule(self, schedule, debug=False) -> None:
        self.track_event(
            category="meltano schedule",
            action=
            f"meltano schedule {schedule.name} {schedule.extractor} {schedule.loader} {schedule.interval} --transform={schedule.transform}",
            debug=debug,
        )

    def track_meltano_select(
        self,
        extractor: str,
        entities_filter: str,
        attributes_filter: str,
        flags: [],
        debug: bool = False,
    ) -> None:
        action = f"meltano select {extractor} {entities_filter} {attributes_filter}"

        if flags["list"]:
            action = action + " --list"
        if flags["all"]:
            action = action + " --all"
        if flags["exclude"]:
            action = action + " --exclude"

        event = self.track_event(category="meltano select",
                                 action=action,
                                 debug=debug)

    def track_meltano_ui(self, debug: bool = False) -> None:
        action = f"meltano ui"
        event = self.track_event(category="meltano ui",
                                 action=action,
                                 debug=debug)
def subject(project):
    return ProjectSettingsService(project)
Esempio n. 20
0
def create_app(config={}):
    project = Project.find()
    setup_logging(project)

    settings_service = ProjectSettingsService(project)

    project_engine(project, default=True)

    app = Flask(
        __name__, instance_path=str(project.root), instance_relative_config=True
    )

    # make sure we have the latest environment loaded
    importlib.reload(meltano.api.config)

    app.config.from_object("meltano.api.config")
    app.config.from_mapping(**meltano.api.config.ProjectSettings(project).as_dict())
    app.config.from_mapping(**config)

    # File logging
    file_handler = logging.handlers.RotatingFileHandler(
        str(project.run_dir("meltano-ui.log")), backupCount=3
    )
    formatter = logging.Formatter(fmt=FORMAT)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

    # 1) Extensions
    security_options = {}

    from .models import db
    from .mail import mail
    from .executor import setup_executor
    from .security import security, users, setup_security
    from .security.oauth import setup_oauth
    from .json import setup_json

    db.init_app(app)
    mail.init_app(app)
    setup_executor(app, project)
    setup_security(app, project)
    setup_oauth(app)
    setup_json(app)

    # we need to setup CORS for development
    if app.env == "development":
        CORS(app, origins="http://localhost:8080", supports_credentials=True)

    # 2) Register the URL Converters
    from .url_converters import PluginRefConverter

    app.url_map.converters["plugin_ref"] = PluginRefConverter

    # 3) Register the controllers

    from .controllers.dashboards import dashboardsBP
    from .controllers.embeds import embedsBP
    from .controllers.reports import reportsBP
    from .controllers.repos import reposBP
    from .controllers.settings import settingsBP
    from .controllers.sql import sqlBP
    from .controllers.orchestrations import orchestrationsBP
    from .controllers.plugins import pluginsBP
    from .controllers.root import root, api_root

    app.register_blueprint(dashboardsBP)
    app.register_blueprint(embedsBP)
    app.register_blueprint(reportsBP)
    app.register_blueprint(reposBP)
    app.register_blueprint(settingsBP)
    app.register_blueprint(sqlBP)
    app.register_blueprint(orchestrationsBP)
    app.register_blueprint(pluginsBP)
    app.register_blueprint(root)
    app.register_blueprint(api_root)

    if app.config["PROFILE"]:
        from .profiler import init

        init(app)

    # Notifications
    if settings_service.get("ui.notification"):
        from .events import notifications

        notifications.init_app(app)
        logger.debug("Notifications are enabled.")
    else:
        logger.debug("Notifications are disabled.")

    # Google Analytics setup
    tracker = GoogleAnalyticsTracker(project)

    @app.before_request
    def setup_js_context():
        # setup the appUrl
        appUrl = urlsplit(request.host_url)
        g.jsContext = {"appUrl": appUrl.geturl()[:-1], "version": meltano.__version__}

        setting_map = {
            "isSendAnonymousUsageStats": "send_anonymous_usage_stats",
            "projectId": "project_id",
            "trackingID": "tracking_ids.ui",
            "embedTrackingID": "tracking_ids.ui_embed",
            "isProjectReadonlyEnabled": "project_readonly",
            "isReadonlyEnabled": "ui.readonly",
            "isAnonymousReadonlyEnabled": "ui.anonymous_readonly",
            "isNotificationEnabled": "ui.notification",
            "isAnalysisEnabled": "ui.analysis",
            "logoUrl": "ui.logo_url",
            "oauthServiceUrl": "oauth_service.url",
        }

        for context_key, setting_name in setting_map.items():
            g.jsContext[context_key] = settings_service.get(setting_name)

        providers = settings_service.get("oauth_service.providers")
        g.jsContext["oauthServiceProviders"] = [
            provider for provider in providers.split(",") if provider
        ]

    @app.after_request
    def after_request(res):
        res.headers[VERSION_HEADER] = meltano.__version__
        return res

    @app.errorhandler(500)
    def internal_error(exception):
        logger.info(f"Error: {exception}")
        return jsonify({"error": True, "code": str(exception)}), 500

    @app.errorhandler(ProjectReadonly)
    def _handle(ex):
        return (jsonify({"error": True, "code": str(ex)}), HTTP_READONLY_CODE)

    # create the dispatcher to host the `OAuthService`
    app.wsgi_app = DispatcherMiddleware(
        app.wsgi_app, {"/-/oauth": create_oauth_service()}
    )

    return app
Esempio n. 21
0
 def __init__(self, project: Project):
     self.settings_service = ProjectSettingsService(project)
Esempio n. 22
0
def test_tracking_disabled(project):
    assert os.getenv("MELTANO_DISABLE_TRACKING") == "True"
    assert ProjectSettingsService(project).get(
        "send_anonymous_usage_stats") == False
Esempio n. 23
0
 def __init__(self, project: Project):
     self.project = project
     self.project_id = ProjectSettingsService(
         self.project).get("project_id")
Esempio n. 24
0
import importlib
import logging
import os
import warnings

import meltano
from gunicorn.glogging import CONFIG_DEFAULTS
from meltano.core.logging.utils import FORMAT
from meltano.core.project import Project
from meltano.core.project_settings_service import ProjectSettingsService

_project = Project.find()
_settings_service = ProjectSettingsService(_project)

loglevel = _settings_service.get("cli.log_level")

logconfig_dict = CONFIG_DEFAULTS.copy()
logconfig_dict["loggers"]["gunicorn.access"]["propagate"] = False
logconfig_dict["loggers"]["gunicorn.error"]["propagate"] = False
logconfig_dict["formatters"]["generic"] = {"format": FORMAT}

if loglevel != "debug":
    logconfig_dict["loggers"].pop("gunicorn.access")

_bind_host = _settings_service.get("ui.bind_host")
_bind_port = _settings_service.get("ui.bind_port")
bind = f"{_bind_host}:{_bind_port}"

workers = _settings_service.get("ui.workers")
forwarded_allow_ips = _settings_service.get("ui.forwarded_allow_ips")