Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions pkg/microservice/aslan/core/common/service/kube/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,33 +397,65 @@ func CheckResourceAppliedByOtherEnv(serviceYaml string, productInfo *commonmodel
return fmt.Errorf("failed to convert manifest to resource, error: %v", err)
}

sharedNSEnvList := make(map[string]*commonmodels.Product)
insertEnvData := func(resource string, env *commonmodels.Product) {
sharedNSEnvList[resource] = env
return checkResourceAppliedByOtherEnv(unstructuredRes, productInfo, serviceName)
}

func checkResourceAppliedByOtherEnv(unstructuredRes []*unstructured.Unstructured, productInfo *commonmodels.Product, serviceName string) error {
if len(unstructuredRes) == 0 {
return nil
}

resSet := sets.NewString()
resources := UnstructuredToResources(unstructuredRes)
hasClusterScopedResource := false
for _, resource := range resources {
if isClusterScopedK8sServiceResource(resource.GroupVersionKind.Kind) {
hasClusterScopedResource = true
break
}
}

var envs []*commonmodels.Product
var err error
if hasClusterScopedResource {
envs, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ClusterID: productInfo.ClusterID})
} else {
envs, err = commonrepo.NewProductColl().ListEnvByNamespace(productInfo.ClusterID, productInfo.Namespace)
}
if err != nil {
log.Errorf("Failed to list environments for resource ownership check, error: %s", err)
return err
}

resSet := sets.NewString()
for _, res := range resources {
resSet.Insert(res.String())
}
log.Infof("checkResourceAppliedByOtherEnv %s/%s, clusterID: %s, namespace: %s, resource: %v ", productInfo.ProductName, productInfo.EnvName, productInfo.ClusterID, productInfo.Namespace, resSet.List())
return checkResourcesAppliedByOtherEnvs(resources, productInfo, serviceName, envs)
}

envs, err := commonrepo.NewProductColl().ListEnvByNamespace(productInfo.ClusterID, productInfo.Namespace)
if err != nil {
log.Errorf("Failed to list existed namespace from the env List, error: %s", err)
return err
func checkResourcesAppliedByOtherEnvs(resources []*commonmodels.ServiceResource, productInfo *commonmodels.Product, serviceName string, envs []*commonmodels.Product) error {
namespacedResSet := sets.NewString()
clusterResSet := sets.NewString()
for _, res := range resources {
if isClusterScopedK8sServiceResource(res.GroupVersionKind.Kind) {
clusterResSet.Insert(res.String())
} else {
namespacedResSet.Insert(res.String())
}
}

sharedNSEnvList := make(map[string]*commonmodels.Product)

for _, env := range envs {
for _, svc := range env.GetServiceMap() {
if env.ProductName == productInfo.ProductName && env.EnvName == productInfo.EnvName && svc.ServiceName == serviceName {
continue
}
for _, res := range svc.Resources {
if resSet.Has(res.String()) {
insertEnvData(res.String(), env)
resourceKey := res.String()
if clusterResSet.Has(resourceKey) || env.Namespace == productInfo.Namespace && namespacedResSet.Has(resourceKey) {
sharedNSEnvList[res.String()] = env
break
}
}
Expand All @@ -441,6 +473,19 @@ func CheckResourceAppliedByOtherEnv(serviceYaml string, productInfo *commonmodel
return fmt.Errorf("resource is applied by other envs: %v", strings.Join(usedEnvStr, ","))
}

func FormatK8sResourceKey(apiVersion, kind, name string) string {
return fmt.Sprintf("%s/%s/%s", apiVersion, kind, name)
}

func isClusterScopedK8sServiceResource(kind string) bool {
switch kind {
case setting.ClusterRole, setting.ClusterRoleBinding:
return true
default:
return false
}
}

func IsStatefulSetStuckInUpdate(sts *appsv1.StatefulSet, log *zap.SugaredLogger) bool {
if sts == nil {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ import (
)

type DeleteProductServicesRequest struct {
ServiceNames []string `json:"service_names"`
ServiceNames []string `json:"service_names"`
DeleteResources map[string][]*service.K8sServiceResource `json:"delete_resources,omitempty"`
}

type DeleteProductHelmReleaseRequest struct {
Expand Down Expand Up @@ -1743,7 +1744,7 @@ func DeleteProductServices(c *gin.Context) {

detail := fmt.Sprintf("%s:[%s]", envName, strings.Join(args.ServiceNames, ","))
internalhandler.InsertDetailedOperationLog(c, ctx.UserName, projectKey, setting.OperationSceneEnv, "删除", "环境的服务", detail, detail, "", types.RequestBodyTypeJSON, ctx.Logger, envName)
ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, envName, projectKey, args.ServiceNames, production, isDelete, ctx.Logger)
ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, envName, projectKey, args.ServiceNames, production, isDelete, args.DeleteResources, ctx.Logger)
}

// @Summary Delete helm release from envrionment
Expand Down
4 changes: 2 additions & 2 deletions pkg/microservice/aslan/core/environment/handler/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func OpenAPIDeleteYamlServiceFromEnv(c *gin.Context) {
}
}

ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, req.EnvName, projectKey, req.ServiceNames, false, !req.NotDeleteResource, ctx.Logger)
ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, req.EnvName, projectKey, req.ServiceNames, false, !req.NotDeleteResource, nil, ctx.Logger)
}

func OpenAPIDeleteProductionYamlServiceFromEnv(c *gin.Context) {
Expand Down Expand Up @@ -550,7 +550,7 @@ func OpenAPIDeleteProductionYamlServiceFromEnv(c *gin.Context) {
return
}

ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, req.EnvName, projectKey, req.ServiceNames, true, !req.NotDeleteResource, ctx.Logger)
ctx.RespErr = service.DeleteProductServices(ctx.UserName, ctx.RequestID, req.EnvName, projectKey, req.ServiceNames, true, !req.NotDeleteResource, nil, ctx.Logger)
}

func OpenAPIApplyProductionYamlService(c *gin.Context) {
Expand Down
1 change: 1 addition & 0 deletions pkg/microservice/aslan/core/environment/handler/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func (*Router) Inject(router *gin.RouterGroup) {
environments.GET("/:name/services/:serviceName", GetService)
environments.PUT("/:name/services/:serviceName", UpdateService)
environments.GET("/:name/services/:serviceName/yaml", FetchServiceYaml)
environments.GET("/:name/services/:serviceName/resources", ListServiceResources)
environments.POST("/:name/services/:serviceName/preview", PreviewService)
environments.POST("/:name/services/preview/batch", BatchPreviewServices)
environments.POST("/:name/services/:serviceName/restart", RestartService)
Expand Down
66 changes: 66 additions & 0 deletions pkg/microservice/aslan/core/environment/handler/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ type FetchServiceYamlResponse struct {
Yaml string `json:"yaml"`
}

type ListServiceResourcesResponse struct {
Resources []*service.K8sServiceResource `json:"resources"`
}

// @Summary Fetch Service Yaml
// @Description Fetch Service Yaml
// @Tags environment
Expand Down Expand Up @@ -284,6 +288,68 @@ func FetchServiceYaml(c *gin.Context) {
ctx.Resp = resp
}

// @Summary List service resources
// @Description List k8s yaml service resources
// @Tags environment
// @Accept json
// @Produce json
// @Param projectName query string true "project name"
// @Param name path string true "env name"
// @Param serviceName path string true "service name"
// @Success 200 {object} ListServiceResourcesResponse
// @Router /api/aslan/environment/environments/{name}/services/{serviceName}/resources [get]
func ListServiceResources(c *gin.Context) {
ctx, err := internalhandler.NewContextWithAuthorization(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
if err != nil {
ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err)
ctx.UnAuthorized = true
return
}

serviceName := c.Param("serviceName")
envName := c.Param("name")
projectKey := c.Query("projectName")
production := c.Query("production") == "true"

// authorization checks
if !ctx.Resources.IsSystemAdmin {
if _, ok := ctx.Resources.ProjectAuthInfo[projectKey]; !ok {
ctx.UnAuthorized = true
return
}
if production {
if !ctx.Resources.ProjectAuthInfo[projectKey].IsProjectAdmin &&
!ctx.Resources.ProjectAuthInfo[projectKey].ProductionEnv.View {
permitted, err := internalhandler.GetCollaborationModePermission(ctx.UserID, projectKey, types.ResourceTypeEnvironment, envName, types.ProductionEnvActionView)
if err != nil || !permitted {
ctx.UnAuthorized = true
return
}
}

err = commonutil.CheckZadigProfessionalLicense()
if err != nil {
ctx.RespErr = err
return
}
} else {
if !ctx.Resources.ProjectAuthInfo[projectKey].IsProjectAdmin &&
!ctx.Resources.ProjectAuthInfo[projectKey].Env.View {
permitted, err := internalhandler.GetCollaborationModePermission(ctx.UserID, projectKey, types.ResourceTypeEnvironment, envName, types.EnvActionView)
if err != nil || !permitted {
ctx.UnAuthorized = true
return
}
}
}
}

resp := new(ListServiceResourcesResponse)
resp.Resources, ctx.RespErr = service.ListK8sServiceResources(projectKey, envName, serviceName, production, ctx.Logger)
ctx.Resp = resp
}

// @Summary Preview service
// @Description Preview service
// @Tags environment
Expand Down
22 changes: 18 additions & 4 deletions pkg/microservice/aslan/core/environment/service/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2575,7 +2575,7 @@ func DeleteProduct(username, envName, productName, requestID string, isDelete bo
}

// @todo fix env already deleted issue, may cause service not really deleted in k8s
err = DeleteProductServices("", requestID, envName, productName, svcNames, false, isDelete, log)
err = DeleteProductServices("", requestID, envName, productName, svcNames, false, isDelete, nil, log)
if err != nil {
log.Warnf("DeleteProductServices error: %v", err)
}
Expand Down Expand Up @@ -2618,7 +2618,7 @@ func DeleteProduct(username, envName, productName, requestID string, isDelete bo
return nil
}

func DeleteProductServices(userName, requestID, envName, productName string, serviceNames []string, production, isDelete bool, log *zap.SugaredLogger) (err error) {
func DeleteProductServices(userName, requestID, envName, productName string, serviceNames []string, production, isDelete bool, deleteResources map[string][]*K8sServiceResource, log *zap.SugaredLogger) (err error) {
productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName, Production: util.GetBoolPointer(production)})
if err != nil {
err = fmt.Errorf("failed to find product, productName: %s, envName: %s, production: %v, error: %v", productName, envName, production, err)
Expand All @@ -2628,7 +2628,7 @@ func DeleteProductServices(userName, requestID, envName, productName string, ser
if getProjectType(productName) == setting.HelmDeployType {
return deleteHelmProductServices(userName, requestID, productInfo, serviceNames, isDelete, log)
}
return deleteK8sProductServices(userName, productInfo, serviceNames, isDelete, log)
return deleteK8sProductServices(userName, productInfo, serviceNames, isDelete, deleteResources, log)
}

func DeleteProductHelmReleases(userName, requestID, envName, productName string, releases []string, production, isDelete bool, log *zap.SugaredLogger) (err error) {
Expand All @@ -2644,7 +2644,7 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo
return kube.DeleteHelmServiceFromEnv(userName, requestID, productInfo, serviceNames, isDelete, log)
}

func deleteK8sProductServices(userName string, productInfo *commonmodels.Product, serviceNames []string, isDelete bool, log *zap.SugaredLogger) error {
func deleteK8sProductServices(userName string, productInfo *commonmodels.Product, serviceNames []string, isDelete bool, deleteResources map[string][]*K8sServiceResource, log *zap.SugaredLogger) error {
serviceRelatedYaml := make(map[string]string)
for _, service := range productInfo.GetServiceMap() {
if !commonutil.ServiceIsDeployed(service.ServiceName, productInfo.ServiceDeployStrategy) || !isDelete {
Expand All @@ -2661,6 +2661,20 @@ func deleteK8sProductServices(userName string, productInfo *commonmodels.Product
log.Errorf("failed to remove k8s resources when rendering yaml for service : %s, err: %s", service.ServiceName, err)
return fmt.Errorf("failed to remove k8s resources when rendering yaml for service : %s, err: %s", service.ServiceName, err)
}

if deleteResources != nil {
selectedResources, ok := deleteResources[service.ServiceName]
if ok {
yaml, err = filterSelectedServiceResourceYaml(yaml, selectedResources)
if err != nil {
log.Errorf("failed to filter selected k8s resources for service : %s, err: %s", service.ServiceName, err)
return fmt.Errorf("failed to filter selected k8s resources for service : %s, err: %s", service.ServiceName, err)
}
if len(selectedResources) == 0 {
log.Infof("all k8s resources are retained when deleting service %s in env %s/%s", service.ServiceName, productInfo.ProductName, productInfo.EnvName)
}
}
}
serviceRelatedYaml[service.ServiceName] = yaml
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,6 @@ func updateK8sProduct(exitedProd *commonmodels.Product, user, requestID string,
updateProd.Namespace = exitedProd.Namespace
updateProd.EnvName = envName

svcsToBeAdd := sets.NewString()

// build services
productSvcs := exitedProd.GetServiceMap()
currentSvcSnapshotMap := make(map[string]*commonmodels.ProductService)
Expand Down Expand Up @@ -328,7 +326,6 @@ func updateK8sProduct(exitedProd *commonmodels.Product, user, requestID string,
} else if util.InStringArray(svc.ServiceName, updateRevisionSvc) {
// services to be added
validSvcGroup = append(validSvcGroup, svc)
svcsToBeAdd.Insert(svc.ServiceName)
}
}
svcGroups = append(svcGroups, validSvcGroup)
Expand Down
Loading
Loading