Beispiel #1
0
    def get(self, application_name, cluster_name):
        """Discovers service instances in specified application and cluster.

        The ``read`` authority is **NOT** required because service registry
        is in the public area of Huskar.

        The response looks like::

            {
              "status": "SUCCESS",
              "message": "",
              "data": [
                {
                  "application": "base.foo",
                  "cluster": "stable",
                  "key": "DB_URL",
                  "value": "{...}"
                }
              ]
            }

        If the ``key`` is specified, the ``data`` field in response will be
        an object directly without :js:class:`Array` around.

        :query key: Optional. The same as :ref:`config`.
        :query resolve: Optional. Resolve linked cluster or not. ``0`` or ``1``
                        ``0``: Don't resolve, ``1``: Resolve (default).
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 404: The application, cluster or key is not found.
        :status 200: The result is in the response.
        """
        check_application(application_name)
        check_cluster_name(cluster_name, application_name)
        facade = InstanceFacade(SERVICE_SUBDOMAIN,
                                application_name,
                                include_comment=False)
        key = request.args.get('key')
        resolve = request.args.get('resolve', '1') != '0'
        validate_fields(instance_schema, {
            'key': key,
            'cluster': cluster_name,
            'application': application_name,
        },
                        optional_fields=['key'])

        if key:
            instance = facade.get_instance(cluster_name, key, resolve=resolve)
            if instance is None:
                abort(
                    404, '%s %s/%s/%s does not exist' % (
                        SERVICE_SUBDOMAIN,
                        application_name,
                        cluster_name,
                        key,
                    ))
            return api_response(instance)
        else:
            instance_list = facade.get_instance_list_by_cluster(
                cluster_name, resolve=resolve)
            return api_response(instance_list)
Beispiel #2
0
    def post(self):
        """Changes the stage of route program.

        The site admin authority is required. See :ref:`application_auth` also.

        :<header Authorization: Huskar Token (See :ref:`token`)
        :form application: The name of application
        :form stage: **D**isabled / **C**hecking / **E**nabled /
                     **S**tandardalone.
        :status 400: The parameters are invalid.
        :status 409: The list is modifying by another request.
        :status 200: The operation is success.
        """
        name = request.form['application']
        stage = request.form.get('stage', type=RouteHijack.Mode)
        cluster_name = request.form.get('cluster', OVERALL)
        cluster_list = {OVERALL}.union(self._make_im().list_cluster_names())
        if stage is None:
            abort(400, 'stage is invalid')
        if cluster_name not in cluster_list:
            abort(400, 'cluster is invalid')
        try:
            g.auth.require_admin()
            check_application(name)
        except NoAuthError:
            check_application_auth(name, Authority.WRITE)
        with self._update_hijack_list(cluster_name) as hijack_list:
            old_stage = hijack_list.pop(name, RouteHijack.Mode.disabled.value)
            hijack_list[name] = stage.value
        if old_stage != stage.value:
            audit_log.emit(
                audit_log.types.PROGRAM_UPDATE_ROUTE_STAGE,
                application_name=name, old_stage=old_stage,
                new_stage=stage.value)
        return api_response({'route_stage': hijack_list})
Beispiel #3
0
    def get(self, application_name, cluster_name):
        """Gets the outgoing route of specific cluster.

        Example of response::

           {
             "status": "SUCCESS",
             "message": "",
             "data": {
               "route": [
                 {"application_name": "base.foo", "intent": "direct",
                  "cluster_name": "alta1-channel-stable-1"},
                 {"application_name": "base.bar", "intent": "direct",
                  "cluster_name": "alta1-channel-stable-1"},
                 {"application_name": "base.baz", "intent": "direct",
                  "cluster_name": null},
               ]
             }
           }

        :param application_name: The name of source application.
        :param cluster_name: The name of source cluster.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 200: The result is in the response.
        """
        check_application(application_name)
        check_cluster_name(cluster_name, application_name)
        facade = RouteManagement(huskar_client, application_name, cluster_name)
        route = sorted({
            'application_name': route[0],
            'intent': route[1],
            'cluster_name': route[2],
        } for route in facade.list_route())
        return api_response({'route': route})
Beispiel #4
0
    def get(self, application_name, cluster_name, key):
        """Get the audit logs of specified instance key.

        :param application_name: The name of application.
        :param cluster_name: The name of clsuter.
        :param key: The key of instance.
        :query date: The date specified to search, default is today.
        :query start: The offset of pagination. Default is ``0``.
        :>header Authorization: Huskar Token (See :ref:`token`)
        :status 403: You don't have required authority.
        :status 501: The server is in minimal mode.
        :status 200: The result is in the response.
                     (See :ref:`Audit Log Schema <audit_schema>`)
        """
        check_application(application_name)
        check_cluster_name(cluster_name, application_name)

        start = request.args.get('start', type=int, default=0)
        application = Application.get_by_name(application_name)
        can_view_sensitive_data = AuditLog.can_view_sensitive_data(
            g.auth.id, self.instance_type, application.id)
        items = AuditLog.get_multi_by_instance_index(self.instance_type,
                                                     application.id,
                                                     cluster_name, key)
        items = items[start:start + 20]
        if not can_view_sensitive_data:
            items = [item.desensitize() for item in items]
        return api_response(audit_log_schema.dump(items, many=True).data)
Beispiel #5
0
    def get(self, application_name, cluster_name):
        """Gets the link status of a cluster.

        :param application_name: The name of application.
        :param cluster_name: The name of cluster.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 200: :js:class:`null` or the name of target physical cluster.
        """
        check_application(application_name)
        check_cluster_name(cluster_name, application_name)
        link = ServiceLink.get_link(application_name, cluster_name)
        if link:
            return api_response(link)
        return api_response()
Beispiel #6
0
    def get_request_data(self):
        request_data = request.get_json()
        if not isinstance(request_data, dict):
            abort(400, 'JSON payload must be present and match its schema')

        request_data = event_subscribe_schema.load(request_data).data
        for type_name, application_names in request_data.iteritems():
            for application_name, cluster_names in application_names.items():
                for cluster_name in cluster_names:
                    check_cluster_name(cluster_name, application_name)
                if type_name == CONFIG_SUBDOMAIN:
                    check_application_auth(application_name, Authority.READ)
                else:
                    check_application(application_name)

                tree_holder_cleaner.track(application_name, type_name)

        return request_data
Beispiel #7
0
    def _update(self, application_name, request, infra_type, infra_name):
        check_application_auth(application_name, Authority.WRITE)
        check_infra_type(infra_type)

        scope_type = request.args['scope_type']
        scope_name = request.args['scope_name']
        check_scope(scope_type, scope_name)

        value = request.get_json()
        if not value or not isinstance(value, dict):
            abort(400, 'Unacceptable content type or content body')

        infra_info = InfraInfo(huskar_client.client, application_name,
                               infra_type)
        infra_info.load()
        old_value = infra_info.get_by_name(infra_name, scope_type, scope_name)

        yield application_name, infra_info, scope_type, scope_name, value

        infra_urls = infra_info.extract_urls(value)
        infra_application_names = extract_application_names(infra_urls)
        new_value = infra_info.get_by_name(infra_name, scope_type, scope_name)

        with audit_log(audit_log.types.UPDATE_INFRA_CONFIG,
                       application_name=application_name,
                       infra_type=infra_type,
                       infra_name=infra_name,
                       scope_type=scope_type,
                       scope_name=scope_name,
                       old_value=old_value,
                       new_value=new_value):
            for infra_application_name in infra_application_names:
                check_application(infra_application_name)

            infra_info.save()

            with suppress_exceptions('infra config updating'):
                infra_urls = infra_info.extract_urls(value, as_dict=True)
                infra_applications = extract_application_names(infra_urls)
                for field_name, infra_application_name in \
                        infra_applications.iteritems():
                    InfraDownstream.bind(application_name, infra_type,
                                         infra_name, scope_type, scope_name,
                                         field_name, infra_application_name)
Beispiel #8
0
    def get(self, application_name):
        """Lists clusters of specified application.

        The service resource has a bit difference to switch and config. Its
        cluster names are public. You could read them without providing
        an authorization token.

        :param application_name: The name of application (a.k.a appid).
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 200: The cluster names should be present in the response:
                     ``{"status": "SUCCESS", "data": [{"name": "stable"}]}``
                     In addition, ``physical_name`` will be present in the list
                     item like ``name`` if the resource has support for
                     **cluster linking**, and ``route`` will be present in the
                     list if the resource has support for **route**.
        """
        validate_fields(instance_schema, {'application': application_name})
        if not self.is_public:
            check_application_auth(application_name, Authority.READ)
        else:
            check_application(application_name)
        facade = InstanceFacade(self.subdomain, application_name)
        cluster_list = list(facade.get_cluster_list())
        return api_response(cluster_list)
Beispiel #9
0
    def get(self, application_name):
        """Gets the default route policy of specific application.

        Example of response::

           {
             "status": "SUCCESS",
             "message": "",
             "data": {
               "default_route": {
                 "overall": {
                   "direct": "channel-stable-2"
                 },
                 "altb1": {
                   "direct": "channel-stable-1"
                 }
               },
               "global_default_route": {
                 "direct": "channel-stable-2"
               }
             }
           }

        :param application_name: The name of specific application.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 200: The result is in the response.
        """
        check_application(application_name)
        facade = RouteManagement(huskar_client, application_name, None)
        default_route = facade.get_default_route()
        return api_response({
            'default_route':
            default_route,
            'global_default_route':
            settings.ROUTE_DEFAULT_POLICY
        })
Beispiel #10
0
    def get(self, application_name):
        """List the subscriptions of an application specified with
        the ``application_name``.

        The response looks like::

            {
              "status": "SUCCESS",
              "message": "",
              "data": {
                "webhook_list": [
                  {
                    "webhook_id": 1,
                    "webhook_url": "http://www.example.com",
                    "webhook_type": 0,
                    "event_list": [
                        "CREATE_CONFIG_CLUSTER",
                        "DELETE_CONFIG_CLUSTER",
                        ...
                    ]
                  },
                  ...
                ]
              }
            }

        :param application_name: The name of application.
        :status 200: The request is successful.
        """
        application = check_application(application_name)
        subscriptions = Webhook.search_subscriptions(
            application_id=application.id)
        groups = itertools.groupby(subscriptions, key=attrgetter('webhook_id'))
        webhook_list = []
        for webhook_id, group in groups:
            webhook = Webhook.get(webhook_id)
            webhook_list.append({
                'webhook_id':
                webhook.id,
                'webhook_url':
                webhook.url,
                'webhook_type':
                webhook.hook_type,
                'event_list': [action_types[x.action_type] for x in group]
            })
        return api_response(data={'webhook_list': webhook_list})
Beispiel #11
0
    def delete(self, application_name):
        """Deletes an existed application.

        .. todo:: Extract the 401/404 from 401.

        :param application_name: The name of deleting application.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 400: The application does not exist yet.
        :status 401: The token are invalid or expired.
        :status 403: The token don't have required authority on current
                     application.
        :status 200: The application was deleted successfully.
        """
        application = check_application(application_name)
        require_team_admin_or_site_admin(application.team)

        with audit_log(audit_log.types.ARCHIVE_APPLICATION,
                       application=application, team=application.team):
            application.archive()

        return api_response()
Beispiel #12
0
    def post(self, application_name):
        """Obtains an application token.

        For using this API, you need to have an user token already, and the
        user need to have **write** authority on current application.

        .. note:: Only :ref:`user-token` are acceptable in this API.

        .. todo:: Extract the 401/404 from 401.
        .. todo:: Restrict the app token.

        :param application_name: The name of application (a.k.a appid).
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 400: The application is malformed. If you believe you are not
                     doing anything wrong, please contact the developers of
                     Huskar API (See :ref:`contact`).
        :status 401: The token are invalid or expired.
        :status 403: The token don't have required authority on current
                     application.
        :status 404: The application does not exist. You need to create it in
                     the first.
        :status 200: You could find token from the response body:
                     ``{"status": "SUCCESS", "data": {"token": "..",
                     "expires_in": None}}``
        """
        check_application_auth(application_name, Authority.WRITE)
        if (g.auth.is_application and
                g.auth.username not in settings.AUTH_SPREAD_WHITELIST):
            raise NoAuthError('It is not permitted to exchange token')
        application = check_application(application_name)
        try:
            user = application.setup_default_auth()
        except NameOccupiedError:
            abort(400, 'malformed application: %s' % application_name)

        token = user.generate_token(settings.SECRET_KEY, None)
        return api_response(data={'token': token, 'expires_in': None})
Beispiel #13
0
    def get(self):
        """Exports multiple instances of service, switch or config.

        The ``read`` authority is required. See :ref:`application_auth` also.

        The response looks like::

            {
              "status": "SUCCESS",
              "message": "",
              "data": [
                {
                  "application": "base.foo",
                  "cluster": "stable",
                  "key": "DB_URL",
                  "value": "mysql://",
                  "comment": "..."
                },
              ]
            }

        The content of ``data`` field will be responded directly if the
        ``format`` is ``file``.

        :form application: The name of application which will be exported.
        :form cluster: The name of cluster which will be exported.
        :form format: ``json`` or ``file`` which decides the content type of
                      response.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 200: The request is successful.
        """
        application_name = request.args['application'].strip()
        cluster_name = request.args.get('cluster')
        export_format = request.args.get('format',
                                         default=self.EXPORT_FORMAT_JSON)

        if self.is_public:
            check_application(application_name)
        else:
            check_application_auth(application_name, Authority.READ)

        validate_fields(instance_schema, {
            'application': application_name,
            'cluster': cluster_name,
        },
                        optional_fields=['cluster'])
        facade = InstanceFacade(self.subdomain, application_name,
                                self.has_comment)
        if cluster_name:
            check_cluster_name(cluster_name, application_name)
            if (self.subdomain == CONFIG_SUBDOMAIN
                    and cluster_name == ROUTE_DEFAULT_INTENT
                    and g.cluster_name):
                content = facade.get_merged_instance_list(g.cluster_name)
            else:
                content = facade.get_instance_list_by_cluster(cluster_name)
        else:
            content = facade.get_instance_list()

        if export_format == self.EXPORT_FORMAT_FILE:
            # Don't expose meta while exporting with file.
            content = [{k: v
                        for k, v in item.iteritems() if k != 'meta'}
                       for item in content]

            file_alike = io.BytesIO()
            file_alike.write(json.dumps(content))
            file_alike.seek(0)
            filename = '%s_backup.json' % self.subdomain
            return send_file(file_alike,
                             as_attachment=True,
                             attachment_filename=filename,
                             mimetype='application/octet-stream',
                             add_etags=False,
                             cache_timeout=0)

        if export_format == self.EXPORT_FORMAT_JSON:
            return api_response(content)

        abort(400, 'Unrecognized "format"')
Beispiel #14
0
    def get(self, application_name, cluster_name):
        """Gets the instance list of specified application and cluster.

        The ``read`` authority is required. See :ref:`application_auth` also.

        The response looks like::

            {
              "status": "SUCCESS",
              "message": "",
              "data": [
                {
                  "application": "base.foo",
                  "cluster": "stable",
                  "key": "DB_URL",
                  "value": "mysql://",
                  "comment": "...",
                  "meta": {
                    "last_modified": 1522033534,
                    "created": 1522033534,
                    "version": 1
                  }
                }
              ]
            }

        If the ``key`` is specified, the ``data`` field in response will be
        an object directly without :js:class:`Array` around.

        :param application_name: The name of application.
        :param cluster_name: The name of cluster.
        :query key: Optional. The specified instance will be responded.
        :<header Authorization: Huskar Token (See :ref:`token`)
        :status 404: The application, cluster or key is not found.
        :status 200: The result is in the response.
        """
        if not self.is_public:
            check_application_auth(application_name, Authority.READ)
        else:
            check_application(application_name)

        check_cluster_name(cluster_name, application_name)
        facade = InstanceFacade(self.subdomain, application_name)
        key = request.args.get('key')
        validate_fields(instance_schema, {
            'application': application_name,
            'cluster': cluster_name,
            'key': key
        },
                        optional_fields=['key'])

        if key:
            instance = facade.get_instance(cluster_name, key)
            if instance is None:
                abort(
                    404, '%s %s/%s/%s does not exist' % (
                        self.subdomain,
                        application_name,
                        cluster_name,
                        key,
                    ))
            return api_response(instance)
        else:
            instance_list = facade.get_instance_list_by_cluster(cluster_name)
            return api_response(instance_list)