diff --git a/internal/delivery/api/endpoint.go b/internal/delivery/api/endpoint.go index deb76d8e..2eca135f 100644 --- a/internal/delivery/api/endpoint.go +++ b/internal/delivery/api/endpoint.go @@ -258,6 +258,7 @@ const ( GetPolicyEdit AddPoliciesForStack DeletePoliciesForStack + StackPolicyStatistics // OrganizationPolicyTemplate ListPolicyTemplate diff --git a/internal/delivery/api/generated_endpoints.go.go b/internal/delivery/api/generated_endpoints.go.go index aa43c935..ba06253e 100644 --- a/internal/delivery/api/generated_endpoints.go.go +++ b/internal/delivery/api/generated_endpoints.go.go @@ -807,6 +807,10 @@ var ApiMap = map[Endpoint]EndpointInfo{ Name: "DeletePoliciesForStack", Group: "Policy", }, + StackPolicyStatistics: { + Name: "StackPolicyStatistics", + Group: "Policy", + }, ListPolicyTemplate: { Name: "ListPolicyTemplate", Group: "OrganizationPolicyTemplate", @@ -1288,6 +1292,8 @@ func (e Endpoint) String() string { return "AddPoliciesForStack" case DeletePoliciesForStack: return "DeletePoliciesForStack" + case StackPolicyStatistics: + return "StackPolicyStatistics" case ListPolicyTemplate: return "ListPolicyTemplate" case CreatePolicyTemplate: @@ -1734,6 +1740,8 @@ func GetEndpoint(name string) Endpoint { return AddPoliciesForStack case "DeletePoliciesForStack": return DeletePoliciesForStack + case "StackPolicyStatistics": + return StackPolicyStatistics case "ListPolicyTemplate": return ListPolicyTemplate case "CreatePolicyTemplate": diff --git a/internal/delivery/http/policy.go b/internal/delivery/http/policy.go index c26d0ee5..ef8529a7 100644 --- a/internal/delivery/http/policy.go +++ b/internal/delivery/http/policy.go @@ -40,6 +40,7 @@ type IPolicyHandler interface { AddPoliciesForStack(w http.ResponseWriter, r *http.Request) UpdatePoliciesForStack(w http.ResponseWriter, r *http.Request) DeletePoliciesForStack(w http.ResponseWriter, r *http.Request) + StackPolicyStatistics(w http.ResponseWriter, r *http.Request) } func NewPolicyHandler(u usecase.Usecase) IPolicyHandler { @@ -646,6 +647,51 @@ func (h *PolicyHandler) ListStackPolicyStatus(w http.ResponseWriter, r *http.Req ResponseJSON(w, r, http.StatusOK, out) } +// StackPolicyStatistics godoc +// +// @Tags StackPolicyStatus +// @Summary [ListStackPolicyStatus] 클러스터의 정책과 정책 템플릿, 버전 조회 +// @Description 클러스터의 정책과 정책 템플릿, 버전 등을 포함한 상태 목록을 조회한다. +// @Accept json +// @Produce json +// @Param organizationId path string true "조직 식별자(o로 시작)" +// @Param stackId path string true "스택 식별자" +// @Param pageSize query string false "pageSize" +// @Param pageNumber query string false "pageNumber" +// @Param sortColumn query string false "sortColumn" +// @Param sortOrder query string false "sortOrder" +// @Param filters query []string false "filters" +// @Success 200 {object} domain.ListStackPolicyStatusResponse +// @Router /organizations/{organizationId}/stacks/{stackId}/statistics [get] +// @Security JWT +func (h *PolicyHandler) StackPolicyStatistics(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + organizationId, ok := vars["organizationId"] + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("invalid clusterId"), + "C_INVALID_STACK_ID", "")) + return + } + + stackId, ok := vars["stackId"] + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("invalid clusterId"), + "C_INVALID_STACK_ID", "")) + return + } + + stackPolicyStatistics, err := h.usecase.GetStackPolicyStatistics(r.Context(), + organizationId, domain.ClusterId(stackId)) + + if err != nil { + ErrorJSON(w, r, err) + return + } + + ResponseJSON(w, r, http.StatusOK, stackPolicyStatistics) +} + // GetStackPolicyTemplateStatus godoc // // @Tags StackPolicyStatus diff --git a/internal/policy-template/tkspolicy.go b/internal/policy-template/tkspolicy.go index 11b50a94..c38e8f7a 100644 --- a/internal/policy-template/tkspolicy.go +++ b/internal/policy-template/tkspolicy.go @@ -71,6 +71,10 @@ func (tksPolicy *TKSPolicy) GetPolicyID() string { return tksPolicy.ObjectMeta.Labels[PolicyIDLabel] } +func (tksPolicy *TKSPolicy) GetTemplateID() string { + return tksPolicy.ObjectMeta.Labels[TemplateIDLabel] +} + func (tksPolicy *TKSPolicy) JSON() (string, error) { result, err := json.MarshalIndent(tksPolicy, "", " ") diff --git a/internal/policy-template/tkspolicytemplate.go b/internal/policy-template/tkspolicytemplate.go index 653952dd..9b32d303 100644 --- a/internal/policy-template/tkspolicytemplate.go +++ b/internal/policy-template/tkspolicytemplate.go @@ -149,6 +149,10 @@ func (tksPolicyTemplate *TKSPolicyTemplate) ToUnstructured() (*unstructured.Unst return tksPolicyTemplateUnstructured, nil } +func (tksPolicyTemplate *TKSPolicyTemplate) GetId() string { + return tksPolicyTemplate.ObjectMeta.Labels[TemplateIDLabel] +} + func ApplyTksPolicyTemplateCR(ctx context.Context, primaryClusterId string, tksPolicyTemplate *TKSPolicyTemplate) error { if syncToKubernetes() { dynamicClient, err := kubernetes.GetDynamicClientAdminCluster(ctx) @@ -282,3 +286,44 @@ func UpdateTksPolicyTemplateCR(ctx context.Context, primaryClusterId string, tks return err } + +func ListTksPolicyTemplateCR(ctx context.Context, primaryClusterId string) ([]*TKSPolicyTemplate, error) { + if syncToKubernetes() { + dynamicClient, err := kubernetes.GetDynamicClientAdminCluster(ctx) + + if err != nil { + return nil, err + } + + results, err := dynamicClient.Resource(TKSPolicyTemplateGVR).Namespace(primaryClusterId). + List(ctx, metav1.ListOptions{}) + + if err != nil { + return nil, err + } + + tkspolicytemplates := make([]*TKSPolicyTemplate, len(results.Items)) + + for i, result := range results.Items { + jsonBytes, err := json.Marshal(result.Object) + + if err != nil { + return nil, err + } + + var tksPolicyTemplate TKSPolicyTemplate + err = json.Unmarshal(jsonBytes, &tksPolicyTemplate) + + if err != nil { + return nil, err + } + + tkspolicytemplates[i] = &tksPolicyTemplate + } + + return tkspolicytemplates, nil + } + + tkspolicytemplates := make([]*TKSPolicyTemplate, 0) + return tkspolicytemplates, nil +} diff --git a/internal/route/route.go b/internal/route/route.go index 3175eb04..06d8aca5 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -354,6 +354,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/policies/name/{policyName}/existence", customMiddleware.Handle(internalApi.ExistsPolicyName, http.HandlerFunc(policyHandler.ExistsPolicyName))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}/policies", customMiddleware.Handle(internalApi.AddPoliciesForStack, http.HandlerFunc(policyHandler.AddPoliciesForStack))).Methods(http.MethodPost) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}/policies", customMiddleware.Handle(internalApi.DeletePoliciesForStack, http.HandlerFunc(policyHandler.DeletePoliciesForStack))).Methods(http.MethodPut) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}/statistics", customMiddleware.Handle(internalApi.StackPolicyStatistics, http.HandlerFunc(policyHandler.StackPolicyStatistics))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}/policy-status", customMiddleware.Handle(internalApi.ListStackPolicyStatus, http.HandlerFunc(policyHandler.ListStackPolicyStatus))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/stacks/{stackId}/policy-templates/{policyTemplateId}", customMiddleware.Handle(internalApi.GetStackPolicyTemplateStatus, http.HandlerFunc(policyHandler.GetStackPolicyTemplateStatus))).Methods(http.MethodGet) diff --git a/internal/usecase/policy.go b/internal/usecase/policy.go index 2a7eb41a..c07d62a2 100644 --- a/internal/usecase/policy.go +++ b/internal/usecase/policy.go @@ -43,6 +43,7 @@ type IPolicyUsecase interface { AddPoliciesForClusterID(ctx context.Context, organizationId string, clusterId domain.ClusterId, policyIds []uuid.UUID) (err error) UpdatePoliciesForClusterID(ctx context.Context, organizationId string, clusterId domain.ClusterId, policyIds []uuid.UUID) (err error) DeletePoliciesForClusterID(ctx context.Context, organizationId string, clusterId domain.ClusterId, policyIds []uuid.UUID) (err error) + GetStackPolicyStatistics(ctx context.Context, organizationId string, clusterId domain.ClusterId) (statistics *domain.StackPolicyStatistics, err error) } type PolicyUsecase struct { @@ -939,6 +940,84 @@ func (u *PolicyUsecase) DeletePoliciesForClusterID(ctx context.Context, organiza return u.repo.DeletePoliciesForClusterID(ctx, organizationId, clusterId, policyIds) } +func (u *PolicyUsecase) GetStackPolicyStatistics(ctx context.Context, organizationId string, clusterId domain.ClusterId) (statistics *domain.StackPolicyStatistics, err error) { + organization, err := u.organizationRepo.Get(ctx, organizationId) + + if err != nil { + log.Errorf(ctx, "error is :%s(%T)", err.Error(), err) + + return nil, httpErrors.NewBadRequestError(fmt.Errorf("invalid organizationId"), "C_INVALID_ORGANIZATION_ID", "") + } + + primaryClusterId := organization.PrimaryClusterId + + templateList, err := policytemplate.ListTksPolicyTemplateCR(ctx, primaryClusterId) + if err != nil { + log.Errorf(ctx, "error is :%s(%T)", err.Error(), err) + + return nil, httpErrors.NewInternalServerError(fmt.Errorf("fail to get template list from kubernetes"), "P_FAILED_TO_CALL_KUBERNETES", "") + } + + totalTemplateCount := len(templateList) + outdatedTemplateIds := []string{} + + for _, template := range templateList { + templateId := template.GetId() + + id, err := uuid.Parse(templateId) + + if err != nil { + log.Errorf(ctx, "error is :%s(%T)", err.Error(), err) + continue + } + + version, err := u.templateRepo.GetLatestTemplateVersion(ctx, id) + if err != nil { + log.Errorf(ctx, "error is :%s(%T)", err.Error(), err) + continue + } + + if version != template.Spec.Version { + outdatedTemplateIds = append(outdatedTemplateIds, templateId) + } + } + + outdatedTemplateCount := len(outdatedTemplateIds) + uptodateTemplateCount := totalTemplateCount - outdatedTemplateCount + + policyList, err := policytemplate.ListTksPolicyCR(ctx, primaryClusterId) + if err != nil { + log.Errorf(ctx, "error is :%s(%T)", err.Error(), err) + + return nil, httpErrors.NewInternalServerError(fmt.Errorf("fail to get policy list from kubernetes"), "P_FAILED_TO_CALL_KUBERNETES", "") + } + + outdatedPolicyCount := 0 + + for _, policy := range policyList { + templateId := policy.GetTemplateID() + + if slices.Contains(outdatedTemplateIds, templateId) { + outdatedPolicyCount++ + } + } + + tototalPolicyCount := len(policyList) + + uptodatePolicyCount := tototalPolicyCount - outdatedPolicyCount + + result := domain.StackPolicyStatistics{ + TotalTemplateCount: totalTemplateCount, + UptodateTemplateCount: uptodateTemplateCount, + OutofdateTemplateCount: outdatedTemplateCount, + TotalPolicyCount: tototalPolicyCount, + UptodatePolicyCount: uptodatePolicyCount, + OutofdatePolicyCount: outdatedPolicyCount, + } + + return &result, nil +} + func extractNewTemplateParameter(paramdefs []*domain.ParameterDef, newParamDefs []*domain.ParameterDef) (policyParameters []domain.UpdatedPolicyTemplateParameter, err error) { diffParamDef, err := policytemplate.GetNewParamDefs(paramdefs, newParamDefs) diff --git a/pkg/domain/policy.go b/pkg/domain/policy.go index 5416f828..cdf10c7f 100644 --- a/pkg/domain/policy.go +++ b/pkg/domain/policy.go @@ -205,3 +205,12 @@ type PolicyStatisticsResponse struct { Template TemplateCount `json:"templateCount"` Policy PolicyCount `json:"policyCount"` } + +type StackPolicyStatistics struct { + TotalTemplateCount int `json:"totalTemplateCount"` + UptodateTemplateCount int `json:"uptodateTemplateCount"` + OutofdateTemplateCount int `json:"outofdateTemplateCount"` + TotalPolicyCount int `json:"totalPolicyCount"` + UptodatePolicyCount int `json:"uptodatePolicyCount"` + OutofdatePolicyCount int `json:"outofdatePolicyCount"` +} diff --git a/pkg/httpErrors/errorCode.go b/pkg/httpErrors/errorCode.go index 3b90a6a9..3e0e5457 100644 --- a/pkg/httpErrors/errorCode.go +++ b/pkg/httpErrors/errorCode.go @@ -136,7 +136,7 @@ var errorMap = map[ErrorCode]string{ "P_INVALID_MATCH": "유효하지 않은 match 설정입니다. match 설정을 확인하세요.", "P_FAILED_FETCH_POLICY": "정책 ID에 해당하는 정책을 가져오는데 실패했습니다.", "P_FAILED_FETCH_CLUSTER": "정책의 클러스터 정보를 가져오는데 실패했습니다.", - "P_FAILED_FETCH_TEMPLATE": "정책의 클러스터 정보를 가져오는데 실패했습니다.", + "P_FAILED_FETCH_TEMPLATE": "정책의 템플릿 정보를 가져오는데 실패했습니다.", "P_CALL_TO_APPLY_KUBERNETES": "쿠버네티스 클러스터 호출에 실패했습니다.", "P_FAILED_TO_APPLY_KUBERNETES": "쿠버네티스 클러스터 변경사항 적용에 실패했습니다.", }