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)
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})
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})
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)
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()
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
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)
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)
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 })
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})
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()
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})
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"')
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)