def list(self, request, project_id): slz = FilterNamespacesSLZ(data=request.query_params) slz.is_valid(raise_exception=True) params = slz.validated_data cluster_id = params["cluster_id"] ns_list = self.filter_namespaces(cluster_id) if not ns_list: return Response([]) # check which namespace has the chart_id initialized namespace_ids = [] chart_id = params.get("chart_id") if chart_id: namespace_ids = set( App.objects.filter(project_id=self.project_id, chart__id=chart_id).values_list( "namespace_id", flat=True)) serializer = self.serializer_class(ns_list, many=True) data = serializer.data for item in data: item["has_initialized"] = item["id"] in namespace_ids item['iam_ns_id'] = calc_iam_ns_id(cluster_id, item['name']) return PermsResponse( data, NamespaceRequest(project_id=project_id, cluster_id=cluster_id))
def list(self, request, project_id, cluster_id): """获取 ServiceMonitor 列表""" cluster_map = self._get_cluster_map(project_id) namespace_map = self._get_namespace_map(project_id) if cluster_id not in cluster_map: raise error_codes.APIError(_('集群 ID {} 不合法').format(cluster_id)) client = K8SClient(request.user.token.access_token, project_id, cluster_id, env=None) manifest = client.list_service_monitor() service_monitors = self._handle_items(cluster_id, cluster_map, namespace_map, manifest) # 共享集群需要再过滤下属于当前项目的命名空间 if get_cluster_type(cluster_id) == ClusterType.SHARED: project_namespaces = get_shared_cluster_proj_namespaces( request.ctx_cluster, request.project.english_name) service_monitors = [ sm for sm in service_monitors if sm['namespace'] in project_namespaces ] for m in service_monitors: m['is_system'] = m['namespace'] in constants.SM_NO_PERM_NAMESPACE m['iam_ns_id'] = calc_iam_ns_id(m['cluster_id'], m['namespace']) return PermsResponse( service_monitors, resource_request=NamespaceRequest(project_id=project_id, cluster_id=cluster_id), )
def list(self, request, project_id): """获取所有HPA数据""" cluster_id = request.query_params.get("cluster_id") hpa_list = utils.get_cluster_hpa_list(request, project_id, cluster_id) for h in hpa_list: h['iam_ns_id'] = calc_iam_ns_id(cluster_id, h['namespace']) return PermsResponse( hpa_list, NamespaceRequest(project_id=project_id, cluster_id=cluster_id))
def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: cluster_id = filter_obj.parent['id'] namespace_list = self._list_namespaces(cluster_id) results = [{ 'id': calc_iam_ns_id(cluster_id, ns['name']), 'display_name': ns['name'] } for ns in namespace_list[page_obj.slice_from:page_obj.slice_to]] return ListResult(results=results, count=len(namespace_list))
def _calc_iam_cluster_ns(self, iam_ns_ids: List[str]) -> Dict[str, Dict]: """ 计算出 iam_ns_id 和命名空间信息的映射表 :param iam_ns_ids: iam_ns_id 列表。iam_ns_id 的计算规则查看 calc_iam_ns_id 函数的实现 :return 映射表. 如 {'40000:70815bb9te': {'name': 'test-default', 'creator': 'admin', ...} """ iam_cluster_ns = {} cluster_set = {f"BCS-K8S-{iam_ns_id.split(':')[0]}" for iam_ns_id in iam_ns_ids} shared_cluster_ids = [cluster['cluster_id'] for cluster in get_shared_clusters()] for cluster_id in cluster_set: for ns in self._list_namespaces(cluster_id, shared_cluster_ids): iam_cluster_ns[calc_iam_ns_id(cluster_id, ns['name'])] = ns return iam_cluster_ns
def get(self, request, project_id, ns_id): cluster_type, app_status, app_id, filter_ns_id, request_cluster_id = self.get_filter_params( request, project_id) if filter_ns_id and str(ns_id) != str(filter_ns_id): return APIResponse({"data": {}}) # 获取项目下集群类型 cluster_env_map = self.get_cluster_id_env(request, project_id) # 检查命名空间属于项目 cluster_id, ns_name = self.check_ns_with_project( request, project_id, ns_id, cluster_type, cluster_env_map) # 共享集群不允许通过该接口查询应用 if get_cluster_type(cluster_id) == ClusterType.SHARED: return APIResponse({"data": {}}) inst_name = None if app_id: inst_name = self.get_inst_name(app_id) ns_name_id = self.get_namespace_name_id(request, project_id) # 根据类型进行过滤数据 category = request.GET.get("category") if not category or category not in CATEGORY_MAP.keys(): raise error_codes.CheckFailed(_("类型不正确")) client = k8s_views.GetInstances() ret_data = client.get( request, project_id, ns_id, category, inst_name, app_status, cluster_env_map, cluster_id, ns_name, ns_name_id, ) iam_ns_id = calc_iam_ns_id(cluster_id, ns_name) for inst in ret_data["instance_list"]: inst['iam_ns_id'] = iam_ns_id return PermsResponse( ret_data, NamespaceRequest(project_id=project_id, cluster_id=cluster_id), resource_data={'iam_ns_id': iam_ns_id}, )
def test_fetch_instance_info(self, cluster_id): iam_ns_id = calc_iam_ns_id(cluster_id, 'default') request = factory.post( '/apis/iam/v1/namespaces/', { 'method': MethodType.FETCH_INSTANCE_INFO, 'type': ResourceType.Namespace, 'filter': { 'ids': [iam_ns_id] }, }, ) p_view = ResourceAPIView.as_view() response = p_view(request) data = response.data assert len(data) == 1 assert data[0]['id'] == iam_ns_id assert data[0]['display_name'] == 'default'
def search_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: """支持模糊搜索命名空间""" cluster_id = filter_obj.parent['id'] # 针对搜索关键字过滤命名空间 namespace_list = [ ns for ns in self._list_namespaces( cluster_id, shared_cluster_ids=[cluster['cluster_id'] for cluster in get_shared_clusters()] ) if filter_obj.keyword in ns['name'] ] results = [ {'id': calc_iam_ns_id(cluster_id, ns['name']), 'display_name': ns['name']} for ns in namespace_list[page_obj.slice_from : page_obj.slice_to] ] return ListResult(results=results, count=len(namespace_list))
def _calc_iam_cluster_ns(self, iam_ns_ids: List[str]) -> Dict[str, str]: """ 计算出 iam_ns_id 和命名空间名称的映射表 :param iam_ns_ids: iam_ns_id 列表。iam_ns_id 的计算规则查看 calc_iam_ns_id 函数的实现 :return 映射表 {iam_ns_id: 命名空间名}, 如 {'40000:70815bb9te': 'test-default'} """ iam_cluster_ns = {} cluster_set = { f"BCS-K8S-{iam_ns_id.split(':')[0]}" for iam_ns_id in iam_ns_ids } for cluster_id in cluster_set: for ns in self._list_namespaces(cluster_id): iam_cluster_ns[calc_iam_ns_id(cluster_id, ns['name'])] = ns['name'] return iam_cluster_ns
def get(self, request, project_id): """获取项目下的所有命名空间""" # 获取过滤参数 cluster_type, app_status, app_id, ns_id, request_cluster_id = self.get_filter_params( request, project_id) exist_app = request.GET.get("exist_app") # 获取项目类型 project_kind = self.project_kind(request) # 获取项目下集群类型 cluster_list, cluster_env_map = self.get_cluster_id_env( request, project_id) if not cluster_list: return APIResponse({"data": []}) # 获取项目下面的namespace ns_list = self.get_namespace(request, project_id) if not ns_list: return APIResponse({"data": []}) # 组装命名空间数据、命名空间ID、项目下集群信息 ns_map, ns_id_list, cluster_id_list, ns_name_list = self.get_cluster_ns( ns_list, cluster_type, ns_id, cluster_env_map, request_cluster_id) # 匹配集群的环境 cluster_env = { info["cluster_id"]: { 'env_type': CLUSTER_ENV_MAP.get(info["environment"], "stag"), 'name': info['name'] } for info in cluster_list } inst_name = None if app_id: inst_name = self.get_inst_name(app_id) category = request.GET.get("category") if not category or category not in CATEGORY_MAP.keys(): raise error_codes.CheckFailed(_("类型不正确")) client = k8s_views.GetNamespace() ns_app, ns_inst_error_count, create_error, all_ns_inst_count = client.get( request, ns_id_list, category, ns_map, project_id, project_kind, self.get_app_deploy_with_post, inst_name, ns_name_list, cluster_id_list, ) # 匹配数据 self.compose_data(ns_map, cluster_env, ns_app, exist_app, ns_inst_error_count, create_error, app_status, all_ns_inst_count) ret_data = list(ns_map.values()) iam_ns_ids = set() for ns in ret_data: iam_ns_id = calc_iam_ns_id(ns['cluster_id'], ns['name']) ns['iam_ns_id'] = iam_ns_id iam_ns_ids.add(iam_ns_id) return PermsResponse( ret_data, NamespaceRequest(project_id=project_id, cluster_id=request_cluster_id))
def list(self, request, project_id): """命名空间列表 权限控制: 必须有对应集群的使用权限 """ access_token = request.user.token.access_token valid_group_by = ['env_type', 'cluster_id', 'cluster_name'] group_by = request.GET.get('group_by') cluster_id = request.GET.get('cluster_id') # 获取全部namespace,前台分页 result = paas_cc.get_namespace_list(access_token, project_id, limit=LIMIT_FOR_ALL_DATA) if result.get('code') != 0: raise error_codes.APIError.f(result.get('message', '')) results = result["data"]["results"] or [] # 针对k8s集群过滤掉平台命名空间 results = self._ignore_ns_for_k8s(results) # 补充cluster_name字段 cluster_list = get_clusters(access_token, project_id) # 添加共享集群 cluster_list = append_shared_clusters(cluster_list) # TODO: 后续发现cluster_id不存在时,再处理 cluster_dict = {i["cluster_id"]: i for i in (cluster_list or [])} # no_vars=1 不显示变量 no_vars = request.GET.get('no_vars') if no_vars == '1': project_var = [] else: project_var = NameSpaceVariable.get_project_ns_vars(project_id) for i in results: # ns_vars = NameSpaceVariable.get_ns_vars(i['id'], project_id) ns_id = i['id'] ns_vars = [] for _var in project_var: _ns_values = _var['ns_values'] _ns_value_ids = _ns_values.keys() ns_vars.append({ 'id': _var['id'], 'key': _var['key'], 'name': _var['name'], 'value': _ns_values.get(ns_id) if ns_id in _ns_value_ids else _var['default_value'], }) i['ns_vars'] = ns_vars if i['cluster_id'] in cluster_dict: i['cluster_name'] = cluster_dict[i['cluster_id']]['name'] i['environment'] = cluster_dict[i['cluster_id']]['environment'] else: i['cluster_name'] = i['cluster_id'] i['environment'] = None if cluster_id: results = filter(lambda x: x['cluster_id'] == cluster_id, results) if group_by and group_by in valid_group_by: # 分组, 排序 results = [{ 'name': k, 'results': sorted(list(v), key=lambda x: x['id'], reverse=True) } for k, v in groupby(sorted(results, key=lambda x: x[group_by]), key=lambda x: x[group_by])] if group_by == 'env_type': ordering = [i.value for i in EnvType] results = sorted(results, key=lambda x: ordering.index(x['name'])) else: results = sorted(results, key=lambda x: x['name'], reverse=True) # 过滤带有ns的集群id cluster_ids_with_ns = [] # 按集群分组时,添加集群环境信息 for r in results: r_ns_list = r.get('results') or [] r_ns = r_ns_list[0] if r_ns_list else {} r['environment'] = r_ns.get('environment', '') r['environment_name'] = get_cluster_env_name( r['environment']) r["cluster_id"] = r_ns.get("cluster_id") if get_cluster_type(r["cluster_id"]) == ClusterType.SHARED: r["is_shared"] = True cluster_ids_with_ns.append(r_ns.get("cluster_id")) # 添加无命名空间集群ID results.extend( self.get_clusters_without_ns(cluster_dict, cluster_ids_with_ns)) else: results = sorted(results, key=lambda x: x['id'], reverse=True) with_perms = str2bool(request.query_params.get('with_perms', True)) if not with_perms: return response.Response(results) namespace_list = [] for namespace in results: namespace['iam_ns_id'] = calc_iam_ns_id(namespace['cluster_id'], namespace['name']) namespace_list.append(namespace) return PermsResponse( namespace_list, NamespaceRequest(project_id=project_id, cluster_id=cluster_id))
def list(self, request, project_id, *args, **kwargs): """""" project_cluster = self.get_project_cluster(request, project_id) qs = self.get_queryset() # 获取过滤参数 params = request.query_params # 集群和命名空间必须传递 cluster_id = params.get('cluster_id') namespace = params.get("namespace") # TODO: 先写入db中,防止前端通过ID,获取数据失败;后续通过helm服务提供API if cluster_id: try: ctx_cluster = CtxCluster.create( id=cluster_id, token=request.user.token.access_token, project_id=project_id) RecordReleases(ctx_cluster, namespace).record() except Exception as e: logger.error("获取集群内release数据失败,%s", e) if cluster_id: qs = qs.filter(cluster_id=cluster_id) if namespace: if not cluster_id: raise ValidationError(_("命名空间作为过滤参数时,需要提供集群ID")) qs = qs.filter(namespace=namespace) # 获取返回的数据 slz = ReleaseListSLZ(qs, many=True) data = slz.data # do fix on the data which version is emtpy iam_ns_ids = [] app_list = [] for item in data: # 过滤掉k8s系统和bcs平台命名空间下的release if item["namespace"] in K8S_PLAT_NAMESPACE: continue cluster_info = project_cluster.get(item['cluster_id']) or { 'name': item['cluster_id'] } item['cluster_name'] = cluster_info['name'] item['iam_ns_id'] = calc_iam_ns_id(item['cluster_id'], item['namespace']) iam_ns_ids.append({'iam_ns_id': item['iam_ns_id']}) item['cluster_env'] = settings.CLUSTER_ENV_FOR_FRONT.get( cluster_info.get('environment')) item["current_version"] = item.pop("version") if not item["current_version"]: version = App.objects.filter(id=item["id"]).values_list( "release__chartVersionSnapshot__version", flat=True)[0] App.objects.filter(id=item["id"]).update(version=version) item["current_version"] = version # 判断任务超时,并更新字段 if self._is_transition_timeout(item["updated"], item["transitioning_on"]): err_msg = _("Helm操作超时,请重试!") App.objects.filter(id=item["id"]).update( transitioning_on=False, transitioning_result=False, transitioning_message=err_msg, ) item["transitioning_result"] = False item["transitioning_on"] = False item["transitioning_message"] = err_msg app_list.append(item) result = { "count": len(app_list), "next": None, "previous": None, "results": app_list } try: ns_request = NamespaceRequest(project_id=project_id, cluster_id=cluster_id) except TypeError: return Response(result) else: return PermsResponse( data=result, resource_request=ns_request, resource_data=iam_ns_ids, )
def handle_data( self, data, s_cate, cluster_id, is_decode, namespace_dict=None, ): for _s in data: _config = _s.get('data', {}) annotations = _config.get('metadata', {}).get('annotations', {}) _s['creator'] = annotations.get(ANNOTATIONS_CREATOR, '') _s['create_time'] = _s.get('createTime', '') _s['update_time'] = annotations.get(ANNOTATIONS_UPDATE_TIME, _s['create_time']) _s['updator'] = annotations.get(ANNOTATIONS_UPDATOR, _s['creator']) _s['status'] = 'Running' _s['can_update'] = True _s['can_update_msg'] = '' _s['can_delete'] = True _s['can_delete_msg'] = '' _s['iam_ns_id'] = calc_iam_ns_id(cluster_id, _s['namespace']) _s['namespace_id'] = namespace_dict.get( (cluster_id, _s['namespace'])) if namespace_dict else None _s['cluster_id'] = cluster_id _s['name'] = _s['resourceName'] labels = _config.get('metadata', {}).get('labels', {}) # 获取模板集信息 template_id = labels.get(LABLE_TEMPLATE_ID) instance_id = labels.get(LABLE_INSTANCE_ID) # 资源来源 source_type = labels.get(SOURCE_TYPE_LABEL_KEY) if not source_type: source_type = "template" if template_id else "other" _s['source_type'] = SOURCE_TYPE_MAP.get(source_type) # 处理 k8s 的系统命名空间的数据 if _s['namespace'] in K8S_SYS_NAMESPACE: _s['can_update'] = _s['can_delete'] = False _s['can_update_msg'] = _s['can_delete_msg'] = _("不允许操作系统命名空间") continue # 处理平台集群和命名空间下的数据 if _s['namespace'] in K8S_PLAT_NAMESPACE: _s['can_update'] = _s['can_delete'] = False _s['can_update_msg'] = _s['can_delete_msg'] = _("不允许操作平台命名空间") continue # 处理创建命名空间时生成的default-token-xxx if s_cate == 'K8sSecret' and _s['name'].startswith( 'default-token'): is_namespace_default_token = True else: is_namespace_default_token = False # 处理系统默认生成的Secret if s_cate == 'K8sSecret' and _s['name'] == '%s%s' % ( K8S_IMAGE_SECRET_PRFIX, _s['namespace']): is_k8s_image_secret = True else: is_k8s_image_secret = False if is_k8s_image_secret or is_namespace_default_token: _s['can_update'] = _s['can_delete'] = False _s['can_update_msg'] = _s['can_delete_msg'] = _("不允许操作系统数据") continue if template_id: try: instance_id = int(instance_id) except Exception: instance_id = 0 else: # 非模板集创建,可以删除但是不可以更新 _s['can_update'] = False _s['can_update_msg'] = _("不是由 表单模版集 实例化生成,无法更新") _s['instance_id'] = instance_id # k8s Secret base64 解码内容 if is_decode and _s['can_update'] and s_cate == 'K8sSecret': _d = _config.get('data') for _key in _d: if _d[_key]: try: _d[_key] = base64.b64decode( _d[_key]).decode("utf-8") except Exception: pass # k8s configmap / secret 获取的是 data 中的数据 ret_data = [] for info in data: if 'createTime' in info: info["createTime"] = ' '.join( CREATE_TIME_REGEX.findall(info["createTime"])[:2]) info_data = getitems(info, ['data', 'data'], {}) if info_data: info['data']['data'] = dict( sorted(info_data.items(), key=lambda x: x[0])) ret_data.append(info)
def get(self, request, project_id): """获取项目下所有的服务""" params = dict(request.GET.items()) params['env'] = 'k8s' # 获取命名空间的id namespace_dict = app_utils.get_ns_id_map( request.user.token.access_token, project_id) # 项目下的所有模板集id all_template_id_list = Template.objects.filter( project_id=project_id, edit_mode=TemplateEditMode.PageForm.value).values_list('id', flat=True) all_template_id_list = [ str(template_id) for template_id in all_template_id_list ] skip_namespace_list = list(K8S_SYS_NAMESPACE) skip_namespace_list.extend(K8S_PLAT_NAMESPACE) cluster_id = params['cluster_id'] code, cluster_services = self.get_services_by_cluster_id( request, params, project_id, cluster_id) if code != ErrorCode.NoError: return Response({'code': code, 'message': cluster_services}) for _s in cluster_services: # NOTE: 兼容处理,因为key: clusterId已被前端使用;通过非bcs创建的service,不一定包含cluster_id _s["clusterId"] = cluster_id _s["cluster_id"] = cluster_id _config = _s.get('data', {}) annotations = _config.get('metadata', {}).get('annotations', {}) _s['update_time'] = annotations.get(ANNOTATIONS_UPDATE_TIME, '') _s['updator'] = annotations.get(ANNOTATIONS_UPDATOR, '') _s['status'] = 'Running' _s['can_update'] = True _s['can_update_msg'] = '' _s['can_delete'] = True _s['can_delete_msg'] = '' namespace_id = namespace_dict.get( (cluster_id, _s['namespace'])) if namespace_dict else None _s['namespace_id'] = namespace_id _s['iam_ns_id'] = calc_iam_ns_id(cluster_id, _s['namespace']) labels = _config.get('metadata', {}).get('labels', {}) template_id = labels.get(LABLE_TEMPLATE_ID) # 资源来源 source_type = labels.get(SOURCE_TYPE_LABEL_KEY) if not source_type: source_type = "template" if template_id else "other" _s['source_type'] = SOURCE_TYPE_MAP.get(source_type) extended_routes = get_svc_extended_routes(project_id, _s['clusterId']) _s['access_info'] = get_svc_access_info(_config, _s['clusterId'], extended_routes) # 处理 k8s 的系统命名空间的数据 if _s['namespace'] in skip_namespace_list: _s['can_update'] = _s['can_delete'] = False _s['can_update_msg'] = _s['can_delete_msg'] = _("不允许操作系统命名空间") continue # 非模板集创建,可以删除但是不可以更新 _s['can_update'] = False _s['can_update_msg'] = _("所属模板集不存在,无法操作") if template_id and template_id in all_template_id_list: _s['can_update'] = True _s['can_update_msg'] = '' # 按时间倒序排列 cluster_services.sort(key=lambda x: x.get('createTime', ''), reverse=True) return PermsResponse( cluster_services, NamespaceRequest(project_id=project_id, cluster_id=params['cluster_id']))
def test_can_not_instantiate_in_ns( self, templateset_permission_obj, namespace_scoped_permission_obj, project_id, template_id, cluster_id, namespace, ): """测试场景:有模板集实例化权限(但是无实例化到命名空间的权限)""" username = roles.PROJECT_TEMPLATESET_USER perm_ctx = TemplatesetPermCtx(username=username, project_id=project_id, template_id=template_id) with pytest.raises(PermissionDeniedError) as exec: templateset_permission_obj.can_instantiate_in_ns( perm_ctx, cluster_id, namespace) iam_ns_id = calc_iam_ns_id(cluster_id, namespace) assert exec.value.data['perms']['apply_url'] == generate_apply_url( username, [ ActionResourcesRequest( NamespaceScopedAction.CREATE, ResourceType.Namespace, resources=[iam_ns_id], parent_chain=[ IAMResource(ResourceType.Project, project_id), IAMResource(ResourceType.Cluster, cluster_id), ], ), ActionResourcesRequest( NamespaceScopedAction.VIEW, ResourceType.Namespace, resources=[iam_ns_id], parent_chain=[ IAMResource(ResourceType.Project, project_id), IAMResource(ResourceType.Cluster, cluster_id), ], ), ActionResourcesRequest( NamespaceScopedAction.UPDATE, ResourceType.Namespace, resources=[iam_ns_id], parent_chain=[ IAMResource(ResourceType.Project, project_id), IAMResource(ResourceType.Cluster, cluster_id), ], ), ActionResourcesRequest( NamespaceScopedAction.DELETE, ResourceType.Namespace, resources=[iam_ns_id], parent_chain=[ IAMResource(ResourceType.Project, project_id), IAMResource(ResourceType.Cluster, cluster_id), ], ), ActionResourcesRequest( NamespaceAction.VIEW, ResourceType.Namespace, resources=[iam_ns_id], parent_chain=[ IAMResource(ResourceType.Project, project_id), IAMResource(ResourceType.Cluster, cluster_id), ], ), ActionResourcesRequest( ClusterAction.VIEW, ResourceType.Cluster, resources=[cluster_id], parent_chain=[ IAMResource(ResourceType.Project, project_id) ], ), ActionResourcesRequest(ProjectAction.VIEW, ResourceType.Project, resources=[project_id]), ], )