예제 #1
0
    def post(self):
        req = request.get_json(True)
        require_fields(req, ('options', 'name', 'type'))

        schema = get_configuration_schema_for_destination_type(req['type'])
        if schema is None:
            abort(400)

        config = ConfigurationContainer(req['options'], schema)
        if not config.is_valid():
            abort(400)

        destination = models.NotificationDestination(org=self.current_org,
                                                     name=req['name'],
                                                     type=req['type'],
                                                     options=config,
                                                     user=self.current_user)

        try:
            models.db.session.add(destination)
            models.db.session.commit()
        except IntegrityError as e:
            if 'name' in e.message:
                abort(400, message=u"Alert Destination with the name {} already exists.".format(req['name']))
            abort(500)

        return destination.to_dict(all=True)
예제 #2
0
    def post(self):
        req = request.get_json(True)
        require_fields(req, ('options', 'name', 'type'))

        schema = get_configuration_schema_for_query_runner_type(req['type'])
        if schema is None:
            abort(400)

        config = ConfigurationContainer(filter_none(req['options']), schema)
        # from IPython import embed
        # embed()
        if not config.is_valid():
            abort(400)

        try:
            datasource = models.DataSource.create_with_group(org=self.current_org,
                                                             name=req['name'],
                                                             type=req['type'],
                                                             options=config)

            models.db.session.commit()
        except IntegrityError as e:
            if req['name'] in e.message:
                abort(400, message="Data source with the name {} already exists.".format(req['name']))

            abort(400)

        self.record_event({
            'action': 'create',
            'object_id': datasource.id,
            'object_type': 'datasource'
        })

        return datasource.to_dict(all=True)
예제 #3
0
    def post(self):
        req = request.get_json(True)
        required_fields = ('options', 'name', 'type')
        for f in required_fields:
            if f not in req:
                abort(400)

        schema = get_configuration_schema_for_type(req['type'])
        if schema is None:
            abort(400)

        config = ConfigurationContainer(req['options'], schema)
        if not config.is_valid():
            abort(400)

        datasource = models.DataSource.create_with_group(org=self.current_org,
                                                         name=req['name'],
                                                         type=req['type'],
                                                         options=config)
        self.record_event({
            'action': 'create',
            'object_id': datasource.id,
            'object_type': 'datasource'
        })

        return datasource.to_dict(all=True)
예제 #4
0
def new(name=None, type=None, options=None):
    """Create new data source."""
    if name is None:
        name = click.prompt("Name")

    if type is None:
        print "Select type:"
        for i, query_runner_name in enumerate(query_runners.keys()):
            print "{}. {}".format(i + 1, query_runner_name)

        idx = 0
        while idx < 1 or idx > len(query_runners.keys()):
            idx = click.prompt("[{}-{}]".format(1, len(query_runners.keys())), type=int)

        type = query_runners.keys()[idx - 1]
    else:
        validate_data_source_type(type)

    if options is None:
        query_runner = query_runners[type]
        schema = query_runner.configuration_schema()

        types = {
            'string': unicode,
            'number': int,
            'boolean': bool
        }

        options_obj = {}

        for k, prop in schema['properties'].iteritems():
            required = k in schema.get('required', [])
            default_value = "<<DEFAULT_VALUE>>"
            if required:
                default_value = None

            prompt = prop.get('title', k.capitalize())
            if required:
                prompt = "{} (required)".format(prompt)
            else:
                prompt = "{} (optional)".format(prompt)

            value = click.prompt(prompt, default=default_value, type=types[prop['type']], show_default=False)
            if value != default_value:
                options_obj[k] = value

        options = ConfigurationContainer(options_obj, schema)
        if not options.is_valid():
            print "Error: invalid configuration."
            exit()

    print "Creating {} data source ({}) with options:\n{}".format(type, name, options)

    data_source = models.DataSource.create(name=name,
                                           type=type,
                                           options=options,
                                           org=models.Organization.get_by_slug('default'))
    print "Id: {}".format(data_source.id)
예제 #5
0
    def test_doesnt_leave_leftovers(self):
        container = ConfigurationContainer({'a': 1, 'b': 'test', 'e': 3}, configuration_schema)
        new_config = container.to_dict(mask_secrets=True)
        new_config.pop('e')
        container.update(new_config)

        self.assertEqual(container['a'], 1)
        self.assertEqual('test', container['b'])
        self.assertNotIn('e', container)
예제 #6
0
class TestConfigurationUpdate(TestCase):
    def setUp(self):
        self.config = {'a': 1, 'b': 'test'}
        self.container = ConfigurationContainer(self.config, configuration_schema)

    def test_rejects_invalid_new_config(self):
        self.assertRaises(ValidationError, lambda: self.container.update({'c': 3}))

    def test_fails_if_no_schema_set(self):
        self.container.set_schema(None)
        self.assertRaises(RuntimeError, lambda: self.container.update({'c': 3}))

    def test_ignores_secret_placehodler(self):
        self.container.update(self.container.to_dict(mask_secrets=True))
        self.assertEqual(self.container['b'], self.config['b'])

    def test_updates_secret(self):
        new_config = {'a': 2, 'b': 'new'}
        self.container.update(new_config)
        self.assertDictEqual(self.container._config, new_config)

    def test_doesnt_leave_leftovers(self):
        container = ConfigurationContainer({'a': 1, 'b': 'test', 'e': 3}, configuration_schema)
        new_config = container.to_dict(mask_secrets=True)
        new_config.pop('e')
        container.update(new_config)

        self.assertEqual(container['a'], 1)
        self.assertEqual('test', container['b'])
        self.assertNotIn('e', container)
예제 #7
0
class TestConfigurationToJson(TestCase):
    def setUp(self):
        self.config = {'a': 1, 'b': 'test'}
        self.container = ConfigurationContainer(self.config, configuration_schema)

    def test_returns_plain_dict(self):
        self.assertDictEqual(self.config, self.container.to_dict())

    def test_raises_exception_when_no_schema_set(self):
        self.container.set_schema(None)
        self.assertRaises(RuntimeError, lambda: self.container.to_dict(mask_secrets=True))

    def test_returns_dict_with_masked_secrets(self):
        d = self.container.to_dict(mask_secrets=True)

        self.assertEqual(d['a'], self.config['a'])
        self.assertNotEqual(d['b'], self.config['b'])

        self.assertEqual(self.config['b'], self.container['b'])
예제 #8
0
파일: data_sources.py 프로젝트: hudl/redash
    def post(self):
        req = request.get_json(True)
        required_fields = ("options", "name", "type")
        for f in required_fields:
            if f not in req:
                abort(400)

        schema = get_configuration_schema_for_query_runner_type(req["type"])
        if schema is None:
            abort(400)

        config = ConfigurationContainer(req["options"], schema)
        if not config.is_valid():
            abort(400)

        datasource = models.DataSource.create_with_group(
            org=self.current_org, name=req["name"], type=req["type"], options=config
        )
        self.record_event({"action": "create", "object_id": datasource.id, "object_type": "datasource"})

        return datasource.to_dict(all=True)
예제 #9
0
파일: destinations.py 프로젝트: hudl/redash
    def post(self):
        req = request.get_json(True)
        required_fields = ('options', 'name', 'type')
        for f in required_fields:
            if f not in req:
                abort(400)

        schema = get_configuration_schema_for_destination_type(req['type'])
        if schema is None:
            abort(400)

        config = ConfigurationContainer(req['options'], schema)
        if not config.is_valid():
            abort(400)

        destination = models.NotificationDestination(org=self.current_org,
                                                     name=req['name'],
                                                     type=req['type'],
                                                     options=config,
                                                     user=self.current_user)
        destination.save()

        return destination.to_dict(all=True)
예제 #10
0
user_factory = ModelFactory(redash.models.User,
                            name='John Doe', email=Sequence('test{}@example.com'),
                            groups=[2],
                            org=1)

org_factory = ModelFactory(redash.models.Organization,
                           name=Sequence("Org {}"),
                           slug=Sequence("org{}.example.com"),
                           settings={})

data_source_factory = ModelFactory(redash.models.DataSource,
                                   name=Sequence('Test {}'),
                                   type='pg',
                                   # If we don't use lambda here it will reuse the same options between tests:
                                   options=lambda: ConfigurationContainer.from_json('{"dbname": "test"}'),
                                   org=1)

dashboard_factory = ModelFactory(redash.models.Dashboard,
                                 name='test', user=user_factory.create, layout='[]', org=1)

api_key_factory = ModelFactory(redash.models.ApiKey,
                               object=dashboard_factory.create)

query_factory = ModelFactory(redash.models.Query,
                             name='New Query',
                             description='',
                             query='SELECT 1',
                             user=user_factory.create,
                             is_archived=False,
                             schedule=None,
예제 #11
0
class DataSource(BelongsToOrgMixin, db.Model):
    id = primary_key("DataSource")
    org_id = Column(key_type("Organization"), db.ForeignKey("organizations.id"))
    org = db.relationship(Organization, backref="data_sources")

    name = Column(db.String(255))
    type = Column(db.String(255))
    options = Column(
        "encrypted_options",
        ConfigurationContainer.as_mutable(
            EncryptedConfiguration(
                db.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine
            )
        ),
    )
    queue_name = Column(db.String(255), default="queries")
    scheduled_queue_name = Column(db.String(255), default="scheduled_queries")
    created_at = Column(db.DateTime(True), default=db.func.now())

    data_source_groups = db.relationship(
        "DataSourceGroup", back_populates="data_source", cascade="all"
    )
    __tablename__ = "data_sources"
    __table_args__ = (db.Index("data_sources_org_id_name", "org_id", "name"),)

    def __eq__(self, other):
        return self.id == other.id

    def __hash__(self):
        return hash(self.id)

    def to_dict(self, all=False, with_permissions_for=None):
        d = {
            "id": self.id,
            "name": self.name,
            "type": self.type,
            "syntax": self.query_runner.syntax,
            "paused": self.paused,
            "pause_reason": self.pause_reason,
            "supports_auto_limit": self.query_runner.supports_auto_limit
        }

        if all:
            schema = get_configuration_schema_for_query_runner_type(self.type)
            self.options.set_schema(schema)
            d["options"] = self.options.to_dict(mask_secrets=True)
            d["queue_name"] = self.queue_name
            d["scheduled_queue_name"] = self.scheduled_queue_name
            d["groups"] = self.groups

        if with_permissions_for is not None:
            d["view_only"] = (
                db.session.query(DataSourceGroup.view_only)
                .filter(
                    DataSourceGroup.group == with_permissions_for,
                    DataSourceGroup.data_source == self,
                )
                .one()[0]
            )

        return d

    def __str__(self):
        return str(self.name)

    @classmethod
    def create_with_group(cls, *args, **kwargs):
        data_source = cls(*args, **kwargs)
        data_source_group = DataSourceGroup(
            data_source=data_source, group=data_source.org.default_group
        )
        db.session.add_all([data_source, data_source_group])
        return data_source

    @classmethod
    def all(cls, org, group_ids=None):
        data_sources = cls.query.filter(cls.org == org).order_by(cls.id.asc())

        if group_ids:
            data_sources = data_sources.join(DataSourceGroup).filter(
                DataSourceGroup.group_id.in_(group_ids)
            )

        return data_sources.distinct()

    @classmethod
    def get_by_id(cls, _id):
        return cls.query.filter(cls.id == _id).one()

    def delete(self):
        Query.query.filter(Query.data_source == self).update(
            dict(data_source_id=None, latest_query_data_id=None)
        )
        QueryResult.query.filter(QueryResult.data_source == self).delete()
        res = db.session.delete(self)
        db.session.commit()

        redis_connection.delete(self._schema_key)

        return res

    def get_cached_schema(self):
        cache = redis_connection.get(self._schema_key)
        return json_loads(cache) if cache else None

    def get_schema(self, refresh=False):
        out_schema = None
        if not refresh:
            out_schema = self.get_cached_schema()

        if out_schema is None:
            query_runner = self.query_runner
            schema = query_runner.get_schema(get_stats=refresh)

            try:
                out_schema = self._sort_schema(schema)
            except Exception:
                logging.exception(
                    "Error sorting schema columns for data_source {}".format(self.id)
                )
                out_schema = schema
            finally:
                redis_connection.set(self._schema_key, json_dumps(out_schema))

        return out_schema

    def _sort_schema(self, schema):
        return [
            {"name": i["name"], "columns": sorted(i["columns"], key=lambda x: x["name"] if isinstance(x, dict) else x)}
            for i in sorted(schema, key=lambda x: x["name"])
        ]

    @property
    def _schema_key(self):
        return "data_source:schema:{}".format(self.id)

    @property
    def _pause_key(self):
        return "ds:{}:pause".format(self.id)

    @property
    def paused(self):
        return redis_connection.exists(self._pause_key)

    @property
    def pause_reason(self):
        return redis_connection.get(self._pause_key)

    def pause(self, reason=None):
        redis_connection.set(self._pause_key, reason or "")

    def resume(self):
        redis_connection.delete(self._pause_key)

    def add_group(self, group, view_only=False):
        dsg = DataSourceGroup(group=group, data_source=self, view_only=view_only)
        db.session.add(dsg)
        return dsg

    def remove_group(self, group):
        DataSourceGroup.query.filter(
            DataSourceGroup.group == group, DataSourceGroup.data_source == self
        ).delete()
        db.session.commit()

    def update_group_permission(self, group, view_only):
        dsg = DataSourceGroup.query.filter(
            DataSourceGroup.group == group, DataSourceGroup.data_source == self
        ).one()
        dsg.view_only = view_only
        db.session.add(dsg)
        return dsg

    @property
    def uses_ssh_tunnel(self):
        return "ssh_tunnel" in self.options

    @property
    def query_runner(self):
        query_runner = get_query_runner(self.type, self.options)

        if self.uses_ssh_tunnel:
            query_runner = with_ssh_tunnel(query_runner, self.options.get("ssh_tunnel"))

        return query_runner

    @classmethod
    def get_by_name(cls, name):
        return cls.query.filter(cls.name == name).one()

    # XXX examine call sites to see if a regular SQLA collection would work better
    @property
    def groups(self):
        groups = DataSourceGroup.query.filter(DataSourceGroup.data_source == self)
        return dict([(group.group_id, group.view_only) for group in groups])
예제 #12
0
            try:
                org = Organization.get_by_slug('default')
                user = User.select().where(User.org==org, peewee.SQL("%s = ANY(groups)", org.admin_group.id)).get()
            except Exception:
                print("!!! Warning: failed finding default organization or admin user, won't migrate Webhook/HipChat alert subscriptions.")
                exit()

            if WEBHOOK_ENDPOINT:
                # Have all existing alerts send to webhook if already configured
                schema = get_configuration_schema_for_destination_type('webhook')
                conf = {'url': WEBHOOK_ENDPOINT}
                if WEBHOOK_USERNAME:
                    conf['username'] = WEBHOOK_USERNAME
                    conf['password'] = WEBHOOK_PASSWORD
                options = ConfigurationContainer(conf, schema)

                webhook = NotificationDestination.create(
                    org=org,
                    user=user,
                    name="Webhook",
                    type="webhook",
                    options=options
                )

                for alert in Alert.select():
                    AlertSubscription.create(
                        user=user,
                        destination=webhook,
                        alert=alert
                    )
예제 #13
0
파일: types.py 프로젝트: ariarijp/redash
 def process_result_value(self, value, dialect):
     return ConfigurationContainer.from_json(super(EncryptedConfiguration, self).process_result_value(value, dialect))
예제 #14
0
 def setUp(self):
     self.config = {'a': 1, 'b': 'test'}
     self.container = ConfigurationContainer(self.config, configuration_schema)
예제 #15
0

user_factory = ModelFactory(redash.models.User,
                            name='John Doe', email=Sequence('test{}@example.com'),
                            groups=[2],
                            org=1)

org_factory = ModelFactory(redash.models.Organization,
                           name=Sequence("Org {}"),
                           slug=Sequence("org{}.example.com"),
                           settings={})

data_source_factory = ModelFactory(redash.models.DataSource,
                                   name=Sequence('Test {}'),
                                   type='pg',
                                   options=ConfigurationContainer.from_json('{"dbname": "test"}'),
                                   org=1)

dashboard_factory = ModelFactory(redash.models.Dashboard,
                                 name='test', user=user_factory.create, layout='[]', org=1)

query_factory = ModelFactory(redash.models.Query,
                             name='New Query',
                             description='',
                             query='SELECT 1',
                             user=user_factory.create,
                             is_archived=False,
                             schedule=None,
                             data_source=data_source_factory.create,
                             org=1)
예제 #16
0
 def test_adds_data_source_to_default_group(self):
     data_source = DataSource.create_with_group(org=self.factory.org, name='test', options=ConfigurationContainer.from_json('{"dbname": "test"}'), type='pg')
     self.assertIn(self.factory.org.default_group.id, data_source.groups)
예제 #17
0
def new(name=None, type=None, options=None):
    """Create new data source."""
    if name is None:
        name = click.prompt("Name")

    if type is None:
        print "Select type:"
        for i, query_runner_name in enumerate(query_runners.keys()):
            print "{}. {}".format(i + 1, query_runner_name)

        idx = 0
        while idx < 1 or idx > len(query_runners.keys()):
            idx = click.prompt("[{}-{}]".format(1, len(query_runners.keys())),
                               type=int)

        type = query_runners.keys()[idx - 1]
    else:
        validate_data_source_type(type)

    query_runner = query_runners[type]
    schema = query_runner.configuration_schema()

    if options is None:
        types = {'string': unicode, 'number': int, 'boolean': bool}

        options_obj = {}

        for k, prop in schema['properties'].iteritems():
            required = k in schema.get('required', [])
            default_value = "<<DEFAULT_VALUE>>"
            if required:
                default_value = None

            prompt = prop.get('title', k.capitalize())
            if required:
                prompt = "{} (required)".format(prompt)
            else:
                prompt = "{} (optional)".format(prompt)

            value = click.prompt(prompt,
                                 default=default_value,
                                 type=types[prop['type']],
                                 show_default=False)
            if value != default_value:
                options_obj[k] = value

        options = ConfigurationContainer(options_obj, schema)
    else:
        options = ConfigurationContainer(json.loads(options), schema)

    if not options.is_valid():
        print "Error: invalid configuration."
        exit()

    print "Creating {} data source ({}) with options:\n{}".format(
        type, name, options.to_json())

    data_source = models.DataSource.create_with_group(
        name=name,
        type=type,
        options=options,
        org=models.Organization.get_by_slug('default'))
    print "Id: {}".format(data_source.id)
예제 #18
0
class DataSource(BelongsToOrgMixin, db.Model):
    id = Column(db.Integer, primary_key=True)
    org_id = Column(db.Integer, db.ForeignKey('organizations.id'))
    org = db.relationship(Organization, backref="data_sources")

    name = Column(db.String(255))
    type = Column(db.String(255))
    options = Column(
        'encrypted_options',
        ConfigurationContainer.as_mutable(
            EncryptedConfiguration(db.Text, settings.SECRET_KEY,
                                   FernetEngine)))
    queue_name = Column(db.String(255), default="queries")
    scheduled_queue_name = Column(db.String(255), default="scheduled_queries")
    created_at = Column(db.DateTime(True), default=db.func.now())

    data_source_groups = db.relationship("DataSourceGroup",
                                         back_populates="data_source",
                                         cascade="all")
    __tablename__ = 'data_sources'
    __table_args__ = (db.Index('data_sources_org_id_name', 'org_id', 'name'), )

    def __eq__(self, other):
        return self.id == other.id

    def to_dict(self, all=False, with_permissions_for=None):
        d = {
            'id': self.id,
            'name': self.name,
            'type': self.type,
            'syntax': self.query_runner.syntax,
            'paused': self.paused,
            'pause_reason': self.pause_reason
        }

        if all:
            schema = get_configuration_schema_for_query_runner_type(self.type)
            self.options.set_schema(schema)
            d['options'] = self.options.to_dict(mask_secrets=True)
            d['queue_name'] = self.queue_name
            d['scheduled_queue_name'] = self.scheduled_queue_name
            d['groups'] = self.groups

        if with_permissions_for is not None:
            d['view_only'] = db.session.query(
                DataSourceGroup.view_only).filter(
                    DataSourceGroup.group == with_permissions_for,
                    DataSourceGroup.data_source == self).one()[0]

        return d

    def __str__(self):
        return text_type(self.name)

    @classmethod
    def create_with_group(cls, *args, **kwargs):
        data_source = cls(*args, **kwargs)
        data_source_group = DataSourceGroup(
            data_source=data_source, group=data_source.org.default_group)
        db.session.add_all([data_source, data_source_group])
        return data_source

    @classmethod
    def all(cls, org, group_ids=None):
        data_sources = cls.query.filter(cls.org == org).order_by(cls.id.asc())

        if group_ids:
            data_sources = data_sources.join(DataSourceGroup).filter(
                DataSourceGroup.group_id.in_(group_ids))

        return data_sources.distinct()

    @classmethod
    def get_by_id(cls, _id):
        return cls.query.filter(cls.id == _id).one()

    def delete(self):
        Query.query.filter(Query.data_source == self).update(
            dict(data_source_id=None, latest_query_data_id=None))
        QueryResult.query.filter(QueryResult.data_source == self).delete()
        res = db.session.delete(self)
        db.session.commit()
        return res

    def get_schema(self):
        schema = []
        tables = TableMetadata.query.filter(
            TableMetadata.data_source_id == self.id).all()
        for table in tables:
            if not table.exists:
                continue

            table_info = {
                'name': table.name,
                'exists': table.exists,
                'hasColumnMetadata': table.column_metadata,
                'columns': []
            }
            columns = ColumnMetadata.query.filter(
                ColumnMetadata.table_id == table.id)
            table_info['columns'] = sorted([{
                'key': column.id,
                'name': column.name,
                'type': column.type,
                'exists': column.exists,
                'example': column.example
            } for column in columns if column.exists == True],
                                           key=itemgetter('name'))
            schema.append(table_info)

        return sorted(schema, key=itemgetter('name'))

    def _pause_key(self):
        return 'ds:{}:pause'.format(self.id)

    @property
    def paused(self):
        return redis_connection.exists(self._pause_key())

    @property
    def pause_reason(self):
        return redis_connection.get(self._pause_key())

    def pause(self, reason=None):
        redis_connection.set(self._pause_key(), reason or '')

    def resume(self):
        redis_connection.delete(self._pause_key())

    def add_group(self, group, view_only=False):
        dsg = DataSourceGroup(group=group,
                              data_source=self,
                              view_only=view_only)
        db.session.add(dsg)
        return dsg

    def remove_group(self, group):
        DataSourceGroup.query.filter(
            DataSourceGroup.group == group,
            DataSourceGroup.data_source == self).delete()
        db.session.commit()

    def update_group_permission(self, group, view_only):
        dsg = DataSourceGroup.query.filter(
            DataSourceGroup.group == group,
            DataSourceGroup.data_source == self).one()
        dsg.view_only = view_only
        db.session.add(dsg)
        return dsg

    @property
    def query_runner(self):
        return get_query_runner(self.type, self.options)

    @classmethod
    def get_by_name(cls, name):
        return cls.query.filter(cls.name == name).one()

    # XXX examine call sites to see if a regular SQLA collection would work better
    @property
    def groups(self):
        groups = DataSourceGroup.query.filter(
            DataSourceGroup.data_source == self)
        return dict(map(lambda g: (g.group_id, g.view_only), groups))
예제 #19
0
                            name='John Doe',
                            email=Sequence('test{}@example.com'),
                            group_ids=[2],
                            org_id=1)

org_factory = ModelFactory(redash.models.Organization,
                           name=Sequence("Org {}"),
                           slug=Sequence("org{}.example.com"),
                           settings={})

data_source_factory = ModelFactory(
    redash.models.DataSource,
    name=Sequence('Test {}'),
    type='pg',
    # If we don't use lambda here it will reuse the same options between tests:
    options=lambda: ConfigurationContainer.from_json('{"dbname": "test"}'),
    org_id=1)

dashboard_factory = ModelFactory(redash.models.Dashboard,
                                 name='test',
                                 user=user_factory.create,
                                 layout='[]',
                                 is_draft=False,
                                 org=1)

api_key_factory = ModelFactory(redash.models.ApiKey,
                               object=dashboard_factory.create)

query_factory = ModelFactory(redash.models.Query,
                             name='Query',
                             description='',
예제 #20
0
def new(name=None, type=None, options=None, organization="default"):
    """Create new data source."""

    if name is None:
        name = click.prompt("Name")

    if type is None:
        print("Select type:")
        for i, query_runner_name in enumerate(query_runners.keys()):
            print("{}. {}".format(i + 1, query_runner_name))

        idx = 0
        while idx < 1 or idx > len(list(query_runners.keys())):
            idx = click.prompt("[{}-{}]".format(1, len(query_runners.keys())), type=int)

        type = list(query_runners.keys())[idx - 1]
    else:
        validate_data_source_type(type)

    query_runner = query_runners[type]
    schema = query_runner.configuration_schema()

    if options is None:
        types = {"string": str, "number": int, "boolean": bool}

        options_obj = {}

        for k, prop in schema["properties"].items():
            required = k in schema.get("required", [])
            default_value = "<<DEFAULT_VALUE>>"
            if required:
                default_value = None

            prompt = prop.get("title", k.capitalize())
            if required:
                prompt = "{} (required)".format(prompt)
            else:
                prompt = "{} (optional)".format(prompt)

            value = click.prompt(
                prompt,
                default=default_value,
                type=types[prop["type"]],
                show_default=False,
            )
            if value != default_value:
                options_obj[k] = value

        options = ConfigurationContainer(options_obj, schema)
    else:
        options = ConfigurationContainer(json_loads(options), schema)

    if not options.is_valid():
        print("Error: invalid configuration.")
        exit()

    print(
        "Creating {} data source ({}) with options:\n{}".format(
            type, name, options.to_json()
        )
    )

    data_source = models.DataSource.create_with_group(
        name=name,
        type=type,
        options=options,
        org=models.Organization.get_by_slug(organization),
    )
    models.db.session.commit()
    print("Id: {}".format(data_source.id))
예제 #21
0
파일: types.py 프로젝트: ariarijp/redash
 def process_result_value(self, value, dialect):
     return ConfigurationContainer.from_json(value)
예제 #22
0
class DataSource(BelongsToOrgMixin, db.Model):
    id = Column(db.Integer, primary_key=True)
    org_id = Column(db.Integer, db.ForeignKey('organizations.id'))
    org = db.relationship(Organization, backref="data_sources")

    name = Column(db.String(255))
    type = Column(db.String(255))
    options = Column(ConfigurationContainer.as_mutable(Configuration))
    queue_name = Column(db.String(255), default="queries")
    scheduled_queue_name = Column(db.String(255), default="scheduled_queries")
    created_at = Column(db.DateTime(True), default=db.func.now())

    data_source_groups = db.relationship("DataSourceGroup", back_populates="data_source",
                                         cascade="all")
    __tablename__ = 'data_sources'
    __table_args__ = (db.Index('data_sources_org_id_name', 'org_id', 'name'),)

    def __eq__(self, other):
        return self.id == other.id

    def to_dict(self, all=False, with_permissions_for=None):
        d = {
            'id': self.id,
            'name': self.name,
            'type': self.type,
            'syntax': self.query_runner.syntax,
            'paused': self.paused,
            'pause_reason': self.pause_reason
        }

        if all:
            schema = get_configuration_schema_for_query_runner_type(self.type)
            self.options.set_schema(schema)
            d['options'] = self.options.to_dict(mask_secrets=True)
            d['queue_name'] = self.queue_name
            d['scheduled_queue_name'] = self.scheduled_queue_name
            d['groups'] = self.groups

        if with_permissions_for is not None:
            d['view_only'] = db.session.query(DataSourceGroup.view_only).filter(
                DataSourceGroup.group == with_permissions_for,
                DataSourceGroup.data_source == self).one()[0]

        return d

    def __str__(self):
        return text_type(self.name)

    @classmethod
    def create_with_group(cls, *args, **kwargs):
        data_source = cls(*args, **kwargs)
        data_source_group = DataSourceGroup(
            data_source=data_source,
            group=data_source.org.default_group)
        db.session.add_all([data_source, data_source_group])
        return data_source

    @classmethod
    def all(cls, org, group_ids=None):
        data_sources = cls.query.filter(cls.org == org).order_by(cls.id.asc())

        if group_ids:
            data_sources = data_sources.join(DataSourceGroup).filter(
                DataSourceGroup.group_id.in_(group_ids))

        return data_sources.distinct()

    @classmethod
    def get_by_id(cls, _id):
        return cls.query.filter(cls.id == _id).one()

    def delete(self):
        Query.query.filter(Query.data_source == self).update(dict(data_source_id=None, latest_query_data_id=None))
        QueryResult.query.filter(QueryResult.data_source == self).delete()
        res = db.session.delete(self)
        db.session.commit()
        return res

    def get_schema(self, refresh=False):
        key = "data_source:schema:{}".format(self.id)

        cache = None
        if not refresh:
            cache = redis_connection.get(key)

        if cache is None:
            query_runner = self.query_runner
            schema = sorted(query_runner.get_schema(get_stats=refresh), key=lambda t: t['name'])

            redis_connection.set(key, json_dumps(schema))
        else:
            schema = json_loads(cache)

        return schema

    def _pause_key(self):
        return 'ds:{}:pause'.format(self.id)

    @property
    def paused(self):
        return redis_connection.exists(self._pause_key())

    @property
    def pause_reason(self):
        return redis_connection.get(self._pause_key())

    def pause(self, reason=None):
        redis_connection.set(self._pause_key(), reason or '')

    def resume(self):
        redis_connection.delete(self._pause_key())

    def add_group(self, group, view_only=False):
        dsg = DataSourceGroup(group=group, data_source=self, view_only=view_only)
        db.session.add(dsg)
        return dsg

    def remove_group(self, group):
        DataSourceGroup.query.filter(
            DataSourceGroup.group == group,
            DataSourceGroup.data_source == self
        ).delete()
        db.session.commit()

    def update_group_permission(self, group, view_only):
        dsg = DataSourceGroup.query.filter(
            DataSourceGroup.group == group,
            DataSourceGroup.data_source == self).one()
        dsg.view_only = view_only
        db.session.add(dsg)
        return dsg

    @property
    def query_runner(self):
        return get_query_runner(self.type, self.options)

    @classmethod
    def get_by_name(cls, name):
        return cls.query.filter(cls.name == name).one()

    # XXX examine call sites to see if a regular SQLA collection would work better
    @property
    def groups(self):
        groups = DataSourceGroup.query.filter(
            DataSourceGroup.data_source == self
        )
        return dict(map(lambda g: (g.group_id, g.view_only), groups))
예제 #23
0
 def test_adds_data_source_to_default_group(self):
     data_source = DataSource.create_with_group(org=self.factory.org, name='test', options=ConfigurationContainer.from_json('{"dbname": "test"}'), type='pg')
     self.assertIn(self.factory.org.default_group.id, data_source.groups)
예제 #24
0
user_factory = ModelFactory(redash.models.User,
                            name='John Doe',
                            email=Sequence('test{}@example.com'),
                            groups=[2],
                            org=1)

org_factory = ModelFactory(redash.models.Organization,
                           name=Sequence("Org {}"),
                           slug=Sequence("org{}.example.com"),
                           settings={})

data_source_factory = ModelFactory(
    redash.models.DataSource,
    name=Sequence('Test {}'),
    type='pg',
    options=ConfigurationContainer.from_json('{"dbname": "test"}'),
    org=1)

dashboard_factory = ModelFactory(redash.models.Dashboard,
                                 name='test',
                                 user=user_factory.create,
                                 layout='[]',
                                 org=1)

api_key_factory = ModelFactory(redash.models.ApiKey,
                               object=dashboard_factory.create)

query_factory = ModelFactory(redash.models.Query,
                             name='New Query',
                             description='',
                             query='SELECT 1',
예제 #25
0
 def process_result_value(self, value, dialect):
     return ConfigurationContainer.from_json(
         super(EncryptedConfiguration,
               self).process_result_value(value, dialect))
예제 #26
0
 def python_value(self, value):
     return ConfigurationContainer.from_json(value)
예제 #27
0
 def setUp(self):
     self.config = {"a": 1, "b": "test"}
     self.container = ConfigurationContainer(self.config, configuration_schema)
예제 #28
0
class NotificationDestination(BelongsToOrgMixin, db.Model):
    id = primary_key("NotificationDestination")
    org_id = Column(key_type("Organization"), db.ForeignKey("organizations.id"))
    org = db.relationship(Organization, backref="notification_destinations")
    user_id = Column(key_type("User"), db.ForeignKey("users.id"))
    user = db.relationship(User, backref="notification_destinations")
    name = Column(db.String(255))
    type = Column(db.String(255))
    options = Column(
        "encrypted_options",
        ConfigurationContainer.as_mutable(
            EncryptedConfiguration(
                db.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine
            )
        ),
    )
    created_at = Column(db.DateTime(True), default=db.func.now())

    __tablename__ = "notification_destinations"
    __table_args__ = (
        db.Index(
            "notification_destinations_org_id_name", "org_id", "name", unique=True
        ),
    )

    def __str__(self):
        return str(self.name)

    def to_dict(self, all=False):
        d = {
            "id": self.id,
            "name": self.name,
            "type": self.type,
            "icon": self.destination.icon(),
        }

        if all:
            schema = get_configuration_schema_for_destination_type(self.type)
            self.options.set_schema(schema)
            d["options"] = self.options.to_dict(mask_secrets=True)

        return d

    @property
    def destination(self):
        return get_destination(self.type, self.options)

    @classmethod
    def all(cls, org):
        notification_destinations = cls.query.filter(cls.org == org).order_by(
            cls.id.asc()
        )

        return notification_destinations

    def notify(self, alert, query, user, new_state, app, host):
        schema = get_configuration_schema_for_destination_type(self.type)
        self.options.set_schema(schema)
        return self.destination.notify(
            alert, query, user, new_state, app, host, self.options
        )
예제 #29
0
 def test_works_for_schema_without_secret(self):
     secretless = configuration_schema.copy()
     secretless.pop("secret")
     container = ConfigurationContainer({"a": 1, "b": "test", "e": 3}, secretless)
     container.update({"a": 2})
     self.assertEqual(container["a"], 2)
예제 #30
0
 def process_result_value(self, value, dialect):
     return ConfigurationContainer.from_json(value)
예제 #31
0
 def setUp(self):
     self.config = {'a': 1, 'b': 'test'}
     self.container = ConfigurationContainer(self.config,
                                             configuration_schema)
예제 #32
0
파일: models.py 프로젝트: Drunkar/redash
 def python_value(self, value):
     return ConfigurationContainer.from_json(value)
 def test_works_for_schema_without_secret(self):
     secretless = configuration_schema.copy()
     secretless.pop('secret')
     container = ConfigurationContainer({'a': 1, 'b': 'test', 'e': 3}, secretless)
     container.update({'a': 2})
     self.assertEqual(container['a'], 2)