func ListAvailablePublicServices(productName string, log *zap.SugaredLogger) ([]*svcInfo, error) {
	project, err := templaterepo.NewProductColl().Find(productName)
	if err != nil {
		log.Errorf("Can not find project %s, error: %s", productName, err)
		return nil, e.ErrListTemplate.AddDesc(err.Error())
	}
	services, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{
		Visibility: setting.PublicService, ExcludeProject: productName,
		NotInServices: project.SharedServices,
	})
	if err != nil {
		log.Errorf("Can not list services, error: %s", err)
		return nil, e.ErrListTemplate.AddDesc(err.Error())
	}
	var res []*svcInfo
	for _, s := range services {
		res = append(res, &svcInfo{
			ServiceName: s.ServiceName,
			ProductName: s.ProductName,
		})
	}
	return res, nil
}
ListAvailablePublicServices returns all public services which are not shared in the given project.
package service
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
	"k8s.io/apimachinery/pkg/util/sets"
	"sigs.k8s.io/yaml"
	configbase "github.com/koderover/zadig/pkg/config"
	"github.com/koderover/zadig/pkg/microservice/aslan/config"
	"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
	commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
	templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template"
	commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb"
	templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template"
	commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service"
	"github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command"
	"github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs"
	"github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube"
	"github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service"
	"github.com/koderover/zadig/pkg/setting"
	"github.com/koderover/zadig/pkg/shared/codehost"
	e "github.com/koderover/zadig/pkg/tool/errors"
	"github.com/koderover/zadig/pkg/tool/gerrit"
	"github.com/koderover/zadig/pkg/tool/httpclient"
	"github.com/koderover/zadig/pkg/tool/kube/getter"
	"github.com/koderover/zadig/pkg/tool/log"
	"github.com/koderover/zadig/pkg/util"
)
type ServiceOption struct {
	ServiceModules []*ServiceModule           `json:"service_module"`
	SystemVariable []*Variable                `json:"system_variable"`
	CustomVariable []*templatemodels.RenderKV `json:"custom_variable"`
	Yaml           string                     `json:"yaml"`
	Service        *commonmodels.Service      `json:"service,omitempty"`
}
type ServiceModule struct {
	*commonmodels.Container
	BuildName string `json:"build_name"`
}
type Variable struct {
	Key         string `json:"key"`
	Value       string `json:"value"`
	Description string `json:"description,omitempty"`
}
type YamlPreviewForPorts struct {
	Kind string `bson:"-"           json:"kind"`
	Spec *struct {
		Ports []struct {
			Name string `bson:"-"           json:"name"`
			Port int    `bson:"-"           json:"port"`
		} `bson:"-"           json:"ports"`
	} `bson:"-"           json:"spec"`
}
type KubeResourceKind struct {
	APIVersion string `yaml:"apiVersion"`
	Kind       string `yaml:"kind"`
	Metadata   struct {
		Name string `yaml:"name"`
	} `yaml:"metadata"`
}
type KubeResource struct {
	APIVersion string `yaml:"apiVersion"`
	Kind       string `yaml:"kind"`
	Metadata   struct {
		Name string `yaml:"name"`
	} `yaml:"metadata"`
	Spec struct {
		Template struct {
			Spec struct {
				Containers []map[string]interface{} `yaml:"containers"`
			} `yaml:"spec"`
		} `yaml:"template"`
	} `yaml:"spec"`
}
type CronjobResource struct {
	APIVersion string `yaml:"apiVersion"`
	Kind       string `yaml:"kind"`
	Metadata   struct {
		Name string `yaml:"name"`
	} `yaml:"metadata"`
	Spec struct {
		Template struct {
			Spec struct {
				Template struct {
					Spec struct {
						Containers []map[string]interface{} `yaml:"containers"`
					} `yaml:"spec"`
				} `yaml:"template"`
			} `yaml:"spec"`
		} `yaml:"jobTemplate"`
	} `yaml:"spec"`
}
type YamlPreview struct {
	Kind string `bson:"-"           json:"kind"`
}
type YamlValidatorReq struct {
	ServiceName string `json:"service_name"`
	Yaml        string `json:"yaml,omitempty"`
}
func ListServicesInExtenalEnv(tmpResp *commonservice.ServiceTmplResp, log *zap.SugaredLogger) {
	opt := &commonrepo.ProductListOptions{
		Source:        setting.SourceFromExternal,
		ExcludeStatus: setting.ProductStatusDeleting,
	}
	products, err := commonrepo.NewProductColl().List(opt)
	if err != nil {
		log.Errorf("Product.List failed, source:%s, err:%v", setting.SourceFromExternal, err)
	} else {
		for _, prod := range products {
			_, services, err := commonservice.ListWorkloadsInEnv(prod.EnvName, prod.ProductName, "", 0, 0, log)
			if err != nil {
				log.Errorf("ListWorkloadsInEnv failed, envName:%s, productName:%s, source:%s, err:%v", prod.EnvName, prod.ProductName, setting.SourceFromExternal, err)
				continue
			}
			for _, service := range services {
				spmap := &commonservice.ServiceProductMap{
					Service:     service.ServiceName,
					Type:        service.Type,
					ProductName: service.ProductName,
					Product:     []string{service.ProductName},
				}
				tmpResp.Data = append(tmpResp.Data, spmap)
			}
		}
	}
}
func GetServiceTemplateOption(serviceName, productName string, revision int64, log *zap.SugaredLogger) (*ServiceOption, error) {
	service, err := commonservice.GetServiceTemplate(serviceName, setting.K8SDeployType, productName, setting.ProductStatusDeleting, revision, log)
	if err != nil {
		return nil, err
	}
	serviceOption, err := GetServiceOption(service, log)
	if serviceOption != nil {
		serviceOption.Service = service
	}
	return serviceOption, err
}
func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*ServiceOption, error) {
	serviceOption := new(ServiceOption)
	serviceModules := make([]*ServiceModule, 0)
	for _, container := range args.Containers {
		serviceModule := new(ServiceModule)
		serviceModule.Container = container
		buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}})
		if buildObj != nil {
			serviceModule.BuildName = buildObj.Name
		}
		serviceModules = append(serviceModules, serviceModule)
	}
	serviceOption.ServiceModules = serviceModules
	serviceOption.SystemVariable = []*Variable{
		{
			Key:   "$Product$",
			Value: args.ProductName},
		{
			Key:   "$Service$",
			Value: args.ServiceName},
		{
			Key:   "$Namespace$",
			Value: ""},
		{
			Key:   "$EnvName$",
			Value: ""},
	}
	renderKVs, err := commonservice.ListServicesRenderKeys([]*templatemodels.ServiceInfo{{Name: args.ServiceName, Owner: args.ProductName}}, log)
	if err != nil {
		log.Errorf("ListServicesRenderKeys %s error: %v", args.ServiceName, err)
		return nil, e.ErrCreateTemplate.AddDesc(err.Error())
	}
	serviceOption.CustomVariable = renderKVs
	prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName)
	if err != nil {
		errMsg := fmt.Sprintf("[ProductTmpl.Find] %s error: %v", args.ProductName, err)
		log.Error(errMsg)
		return nil, e.ErrGetProduct.AddDesc(errMsg)
	}
	err = commonservice.FillProductTemplateVars([]*templatemodels.Product{prodTmpl}, log)
	if err != nil {
		return nil, err
	}
	if prodTmpl.Vars != nil {
		renderKVsMap := make(map[string]*templatemodels.RenderKV)
		for _, serviceRenderKV := range renderKVs {
			renderKVsMap[serviceRenderKV.Key] = serviceRenderKV
		}
		productRenderEnvs := make([]*templatemodels.RenderKV, 0)
		for _, envVar := range prodTmpl.Vars {
			delete(renderKVsMap, envVar.Key)
			renderEnv := &templatemodels.RenderKV{
				Key:      envVar.Key,
				Value:    envVar.Value,
				Alias:    envVar.Alias,
				State:    envVar.State,
				Services: envVar.Services,
			}
			productRenderEnvs = append(productRenderEnvs, renderEnv)
		}
		for _, envVar := range renderKVsMap {
			envVar.State = config.KeyStateNew
			productRenderEnvs = append(productRenderEnvs, envVar)
		}
		serviceOption.CustomVariable = productRenderEnvs
	}
	if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub ||
		args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub ||
		args.Source == setting.SourceFromIlyshin {
		serviceOption.Yaml = args.Yaml
	}
	return serviceOption, nil
}
func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []models.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error {
	kubeClient, err := kube.GetKubeClient(clusterID)
	if err != nil {
		log.Errorf("[%s] error: %v", namespace, err)
		return err
	}
	opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName}
	if _, err := commonrepo.NewProductColl().Find(opt); err == nil {
		log.Errorf("[%s][P:%s] duplicate envName in the same product", envName, productName)
		return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg)
	}
	var (
		workloadsTmp []models.Workload
		mu           sync.Mutex
	)
	serviceString := sets.NewString()
	services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "")
	for _, v := range services {
		serviceString.Insert(v.ServiceName)
	}
	for _, workload := range workLoads {
		if serviceString.Has(workload.Name) {
			return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", workload.Name))
		}
	}
	g := new(errgroup.Group)
	for _, workload := range workLoads {
		tempWorkload := workload
		g.Go(func() error {
			var bs []byte
			switch tempWorkload.Type {
			case setting.Deployment:
				bs, _, err = getter.GetDeploymentYaml(namespace, tempWorkload.Name, kubeClient)
			case setting.StatefulSet:
				bs, _, err = getter.GetStatefulSetYaml(namespace, tempWorkload.Name, kubeClient)
			}
			if len(bs) == 0 || err != nil {
				log.Errorf("not found yaml %v", err)
				return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/sts failed err:%s", err))
			}
			mu.Lock()
			defer mu.Unlock()
			workloadsTmp = append(workloadsTmp, models.Workload{
				EnvName:     envName,
				Name:        tempWorkload.Name,
				Type:        tempWorkload.Type,
				ProductName: productName,
			})
			return CreateWorkloadTemplate(username, &models.Service{
				ServiceName:  tempWorkload.Name,
				Yaml:         string(bs),
				ProductName:  productName,
				CreateBy:     username,
				Type:         setting.K8SDeployType,
				WorkloadType: tempWorkload.Type,
				Source:       setting.SourceFromExternal,
				EnvName:      envName,
				Revision:     1,
			}, log)
		})
	}
	if err := g.Wait(); err != nil {
		return err
	}
	if _, err = commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{
		Name:    productName,
		EnvName: envName,
	}); err != nil {
		if err := service.CreateProduct(username, requestID, &commonmodels.Product{
			ProductName: productName,
			Source:      setting.SourceFromExternal,
			ClusterID:   clusterID,
			EnvName:     envName,
			Namespace:   namespace,
		}, log); err != nil {
			return e.ErrCreateProduct.AddDesc("create product Error for unknown reason")
		}
	}
	workLoadStat, err := commonrepo.NewWorkLoadsStatColl().Find(clusterID, namespace)
	if err != nil {
		workLoadStat = &commonmodels.WorkloadStat{
			ClusterID: clusterID,
			Namespace: namespace,
			Workloads: workloadsTmp,
		}
		return commonrepo.NewWorkLoadsStatColl().Create(workLoadStat)
	}
	workLoadStat.Workloads = replaceWorkloads(workLoadStat.Workloads, workloadsTmp, envName)
	return commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workLoadStat)
}
type ServiceWorkloadsUpdateAction struct {
	EnvName     string `json:"env_name"`
	Name        string `json:"name"`
	Type        string `json:"type"`
	ProductName string `json:"product_name"`
	Operation   string `json:"operation"`
}
type UpdateWorkloadsArgs struct {
	WorkLoads []commonmodels.Workload `json:"workLoads"`
	ClusterID string                  `json:"cluster_id"`
	Namespace string                  `json:"namespace"`
}
func UpdateWorkloads(ctx context.Context, requestID, username, productName, envName string, args UpdateWorkloadsArgs, log *zap.SugaredLogger) error {
	kubeClient, err := kube.GetKubeClient(args.ClusterID)
	if err != nil {
		log.Errorf("[%s] error: %s", args.Namespace, err)
		return err
	}
	workloadStat, err := commonrepo.NewWorkLoadsStatColl().Find(args.ClusterID, args.Namespace)
	if err != nil {
		log.Errorf("[%s][%s]NewWorkLoadsStatColl().Find %s", args.ClusterID, args.Namespace, err)
		return err
	}
	diff := map[string]*ServiceWorkloadsUpdateAction{}
	originSet := sets.NewString()
	uploadSet := sets.NewString()
	for _, v := range workloadStat.Workloads {
		if v.ProductName == productName && v.EnvName == envName {
			originSet.Insert(v.Name)
		}
	}
	for _, v := range args.WorkLoads {
		uploadSet.Insert(v.Name)
	}
	deleteString := originSet.Difference(uploadSet)
	addString := uploadSet.Difference(originSet)
	for _, v := range workloadStat.Workloads {
		if v.ProductName != productName {
			continue
		}
		if deleteString.Has(v.Name) {
			diff[v.Name] = &ServiceWorkloadsUpdateAction{
				EnvName:     envName,
				Name:        v.Name,
				Type:        v.Type,
				ProductName: v.ProductName,
				Operation:   "delete",
			}
		}
	}
	services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "")
	for _, v := range services {
		if addString.Has(v.ServiceName) {
			return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName))
		}
	}
	for _, v := range args.WorkLoads {
		if addString.Has(v.Name) {
			diff[v.Name] = &ServiceWorkloadsUpdateAction{
				EnvName:     envName,
				Name:        v.Name,
				Type:        v.Type,
				ProductName: v.ProductName,
				Operation:   "add",
			}
		}
	}
	for _, v := range diff {
		switch v.Operation {
		case "delete":
			err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName)
			if err != nil {
				log.Errorf("UpdateStatus external services error:%s", err)
			}
		case "add":
			var bs []byte
			switch v.Type {
			case setting.Deployment:
				bs, _, err = getter.GetDeploymentYaml(args.Namespace, v.Name, kubeClient)
			case setting.StatefulSet:
				bs, _, err = getter.GetStatefulSetYaml(args.Namespace, v.Name, kubeClient)
			}
			if len(bs) == 0 || err != nil {
				log.Errorf("UpdateK8sWorkLoads not found yaml %s", err)
				delete(diff, v.Name)
				continue
			}
			if err = CreateWorkloadTemplate(username, &models.Service{
				ServiceName:  v.Name,
				Yaml:         string(bs),
				ProductName:  productName,
				CreateBy:     username,
				Type:         setting.K8SDeployType,
				WorkloadType: v.Type,
				Source:       setting.SourceFromExternal,
				EnvName:      envName,
				Revision:     1,
			}, log); err != nil {
				log.Errorf("create service template failed err:%v", err)
				delete(diff, v.Name)
				continue
			}
		}
	}
	workloadStat.Workloads = updateWorkloads(workloadStat.Workloads, diff, envName, productName)
	return commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat)
}
func updateWorkloads(existWorkloads []models.Workload, diff map[string]*ServiceWorkloadsUpdateAction, envName string, productName string) (result []models.Workload) {
	existWorkloadsMap := map[string]models.Workload{}
	for _, v := range existWorkloads {
		existWorkloadsMap[v.Name] = v
	}
	for _, v := range diff {
		switch v.Operation {
		case "add":
			vv := models.Workload{
				EnvName:     envName,
				Name:        v.Name,
				Type:        v.Type,
				ProductName: productName,
			}
			existWorkloadsMap[v.Name] = vv
		case "delete":
			delete(existWorkloadsMap, v.Name)
		}
	}
	for _, v := range existWorkloadsMap {
		result = append(result, v)
	}
	return result
}
func replaceWorkloads(existWorkloads []models.Workload, newWorkloads []models.Workload, envName string) []models.Workload {
	var result []models.Workload
	workloadMap := map[string]models.Workload{}
	for _, workload := range existWorkloads {
		if workload.EnvName != envName {
			workloadMap[workload.Name] = workload
			result = append(result, workload)
		}
	}
	for _, newWorkload := range newWorkloads {
		if _, ok := workloadMap[newWorkload.Name]; !ok {
			result = append(result, newWorkload)
		}
	}
	return result
}
func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *zap.SugaredLogger) error {
	_, err := templaterepo.NewProductColl().Find(args.ProductName)
	if err != nil {
		log.Errorf("Failed to find project %s, err: %s", args.ProductName, err)
		return e.ErrInvalidParam.AddErr(err)
	}
	args.KubeYamls = []string{args.Yaml}
	if err := setCurrentContainerImages(args); err != nil {
		log.Errorf("Failed tosetCurrentContainerImages %s, err: %s", args.ProductName, err)
		return err
	}
	opt := &commonrepo.ServiceFindOption{
		ServiceName:   args.ServiceName,
		ProductName:   args.ProductName,
		ExcludeStatus: setting.ProductStatusDeleting,
	}
	_, notFoundErr := commonrepo.NewServiceColl().Find(opt)
	if notFoundErr != nil {
		if productTempl, err := commonservice.GetProductTemplate(args.ProductName, log); err == nil {
			if len(productTempl.Services) > 0 && !sets.NewString(productTempl.Services[0]...).Has(args.ServiceName) {
				productTempl.Services[0] = append(productTempl.Services[0], args.ServiceName)
			} else {
				productTempl.Services = [][]string{{args.ServiceName}}
			}
			err = templaterepo.NewProductColl().Update(args.ProductName, productTempl)
			if err != nil {
				log.Errorf("CreateServiceTemplate Update %s error: %s", args.ServiceName, err)
				return e.ErrCreateTemplate.AddDesc(err.Error())
			}
		}
	} else {
		return e.ErrCreateTemplate.AddDesc("do not support import same service name")
	}
	if err := commonrepo.NewServiceColl().Delete(args.ServiceName, args.Type, args.ProductName, setting.ProductStatusDeleting, 0); err != nil {
		log.Errorf("ServiceTmpl.delete %s error: %v", args.ServiceName, err)
	}
	if err := commonrepo.NewServiceColl().Create(args); err != nil {
		log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err)
		return e.ErrCreateTemplate.AddDesc(err.Error())
	}
	return nil
}
func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap.SugaredLogger) (*ServiceOption, error) {
	opt := &commonrepo.ServiceFindOption{
		ServiceName:   args.ServiceName,
		ProductName:   args.ProductName,
		ExcludeStatus: setting.ProductStatusDeleting,
	}
	project, err := templaterepo.NewProductColl().Find(args.ProductName)
	if err != nil {
		log.Errorf("Failed to find project %s, err: %s", args.ProductName, err)
		return nil, e.ErrInvalidParam.AddErr(err)
	}
	if _, ok := project.SharedServiceInfoMap()[args.ServiceName]; ok {
		return nil, e.ErrInvalidParam.AddDesc(fmt.Sprintf("A service with same name %s is already existing", args.ServiceName))
	}
	serviceTmpl, notFoundErr := commonrepo.NewServiceColl().Find(opt)
	if notFoundErr == nil {
		if args.Type == setting.K8SDeployType && args.Source == serviceTmpl.Source {
			if args.Source == setting.SourceFromZadig || args.Source == "" {
				if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml {
					log.Info("Yaml config remains the same, quit creation.")
					return GetServiceOption(serviceTmpl, log)
				}
			}
			if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromIlyshin {
				if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA {
					log.Infof("%s change log remains the same, quit creation", args.Source)
					return GetServiceOption(serviceTmpl, log)
				}
				if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" {
					log.Errorf("Changing load path is not allowed")
					return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径")
				}
			} else if args.Source == setting.SourceFromGerrit {
				if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml {
					log.Info("gerrit Yaml config remains the same, quit creation.")
					return GetServiceOption(serviceTmpl, log)
				}
				if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" {
					log.Errorf("Changing load path is not allowed")
					return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径")
				}
				if err := updateGerritWebhookByService(serviceTmpl, args); err != nil {
					log.Infof("gerrit update webhook err :%v", err)
					return nil, err
				}
			}
		} else if args.Source == setting.SourceFromGerrit {
			err := GetGerritServiceYaml(args, log)
			if err != nil {
				log.Errorf("GetGerritServiceYaml from gerrit failed, error: %v", err)
				return nil, err
			}
			if err = createGerritWebhookByService(args.GerritCodeHostID, args.ServiceName, args.GerritRepoName, args.GerritBranchName); err != nil {
				log.Errorf("createGerritWebhookByService error: %v", err)
				return nil, err
			}
		}
	} else {
		if args.Source == setting.SourceFromGerrit {
			if err := createGerritWebhookByService(args.GerritCodeHostID, args.ServiceName, args.GerritRepoName, args.GerritBranchName); err != nil {
				log.Errorf("createGerritWebhookByService error: %v", err)
				return nil, err
			}
		}
	}
	if err := ensureServiceTmpl(userName, args, log); err != nil {
		log.Errorf("ensureServiceTmpl error: %+v", err)
		return nil, e.ErrValidateTemplate.AddDesc(err.Error())
	}
	if err := commonrepo.NewServiceColl().Delete(args.ServiceName, args.Type, args.ProductName, setting.ProductStatusDeleting, args.Revision); err != nil {
		log.Errorf("ServiceTmpl.delete %s error: %v", args.ServiceName, err)
	}
	if err := commonrepo.NewServiceColl().Create(args); err != nil {
		log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err)
		return nil, e.ErrCreateTemplate.AddDesc(err.Error())
	}
	if notFoundErr != nil {
		if productTempl, err := commonservice.GetProductTemplate(args.ProductName, log); err == nil {
			if len(productTempl.Services) > 0 && !sets.NewString(productTempl.Services[0]...).Has(args.ServiceName) {
				productTempl.Services[0] = append(productTempl.Services[0], args.ServiceName)
			} else {
				productTempl.Services = [][]string{{args.ServiceName}}
			}
			err = templaterepo.NewProductColl().Update(args.ProductName, productTempl)
			if err != nil {
				log.Errorf("CreateServiceTemplate Update %s error: %s", args.ServiceName, err)
				return nil, e.ErrCreateTemplate.AddDesc(err.Error())
			}
		}
	}
	commonservice.ProcessServiceWebhook(args, serviceTmpl, args.ServiceName, log)
	return GetServiceOption(args, log)
}
func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error {
	currentService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{
		ProductName: args.ProductName,
		ServiceName: args.ServiceName,
		Revision:    args.Revision,
	})
	if err != nil {
		log.Errorf("Can not find service with option %+v", args)
		return err
	}
	if currentService.Visibility != args.Visibility && args.Visibility == "private" {
		projects, err := templaterepo.NewProductColl().ListWithOption(
			&templaterepo.ProductListOpt{ContainSharedServices: []*templatemodels.ServiceInfo{{Name: args.ServiceName, Owner: args.ProductName}}},
		)
		if err != nil {
			log.Errorf("Failed to list projects using service %s, err: %s", args.ServiceName, err)
			return err
		}
		if len(projects) > 0 {
			var names []string
			for _, p := range projects {
				names = append(names, p.ProductName)
			}
			log.Warnf("service %s is used by projects %s", args.ServiceName, strings.Join(names, ","))
			errMsg := fmt.Sprintf("共享服务 [%s] 已被 [%s] 等项目服务编排中使用，请解除引用后再修改", args.ServiceName, strings.Join(names, ","))
			return e.ErrInvalidParam.AddDesc(errMsg)
		}
	}
	envStatuses := make([]*commonmodels.EnvStatus, 0)
	for _, envStatus := range args.EnvStatuses {
		var existEnv, existHost bool
		envName := envStatus.EnvName
		if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{
			Name:    args.ProductName,
			EnvName: envName,
		}); err == nil {
			existEnv = true
		}
		op := commonrepo.FindPrivateKeyOption{
			Address: envStatus.Address,
			ID:      envStatus.HostID,
		}
		if _, err := commonrepo.NewPrivateKeyColl().Find(op); err == nil {
			existHost = true
		}
		if existEnv && existHost {
			envStatuses = append(envStatuses, envStatus)
		}
	}
	updateArgs := &commonmodels.Service{
		ProductName: args.ProductName,
		ServiceName: args.ServiceName,
		Visibility:  args.Visibility,
		Revision:    args.Revision,
		Type:        args.Type,
		CreateBy:    args.Username,
		EnvConfigs:  args.EnvConfigs,
		EnvStatuses: envStatuses,
	}
	return commonrepo.NewServiceColl().Update(updateArgs)
}
func YamlValidator(args *YamlValidatorReq) []string {
	errorDetails := make([]string, 0)
	if args.Yaml == "" {
		return errorDetails
	}
	yamlContent := util.ReplaceWrapLine(args.Yaml)
	KubeYamls := SplitYaml(yamlContent)
	for _, data := range KubeYamls {
		yamlDataArray := SplitYaml(data)
		for _, yamlData := range yamlDataArray {
			resKind := new(KubeResourceKind)
			yamlData = config.RenderTemplateAlias.ReplaceAllLiteralString(yamlData, "ssssssss")
			yamlData = config.ServiceNameAlias.ReplaceAllLiteralString(yamlData, args.ServiceName)
			if err := yaml.Unmarshal([]byte(yamlData), &resKind); err != nil {
				log.Errorf("yaml unmarsh err: %s", err)
				errorDetails = append(errorDetails, "Invalid yaml format. The content must be a series of valid Kubernetes resources")
			}
		}
	}
	return errorDetails
}
func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error {
	openEnvTemplate, _ := strconv.ParseBool(isEnvTemplate)
	if openEnvTemplate && visibility == "public" {
		projects, err := templaterepo.NewProductColl().ListWithOption(
			&templaterepo.ProductListOpt{ContainSharedServices: []*templatemodels.ServiceInfo{{Name: serviceName, Owner: productName}}},
		)
		if err != nil {
			log.Errorf("Failed to list projects which are using service %s, err: %s", serviceName, err)
			return err
		}
		if len(projects) > 0 {
			var names []string
			for _, p := range projects {
				names = append(names, p.ProductName)
			}
			log.Warnf("service %s is used by projects %s", serviceName, strings.Join(names, ","))
			errMsg := fmt.Sprintf("共享服务 [%s] 已被 [%s] 等项目服务编排中使用，请解除引用后再删除", serviceName, strings.Join(names, ","))
			return e.ErrInvalidParam.AddDesc(errMsg)
		}
		serviceEnvMap, err := distinctEnvServices(productName)
		if err != nil {
			errMsg := fmt.Sprintf("get distinctServiceEnv error: %v", err)
			log.Error(errMsg)
			return e.ErrDeleteTemplate.AddDesc(errMsg)
		}
		if _, ok := serviceEnvMap[serviceName]; ok {
			var envNames []string
			for _, env := range serviceEnvMap[serviceName] {
				envNames = append(envNames, env.EnvName+"-"+env.ProductName)
			}
			log.Error(fmt.Sprintf("failed to delete service, %s already exists in %s",
				serviceName, strings.Join(envNames, ", ")))
			errMsg := fmt.Sprintf("共享服务 %s 已存在于 %s 等环境中，请对这些环境进行更新后再删除",
				serviceName, strings.Join(envNames, ", "))
			return e.ErrInvalidParam.AddDesc(errMsg)
		}
	}
	if serviceType == setting.PMDeployType {
		if serviceTmpl, err := commonservice.GetServiceTemplate(
			serviceName, setting.PMDeployType, productName, setting.ProductStatusDeleting, 0, log,
		); err == nil {
			if serviceTmpl.BuildName != "" {
				updateTargets := make([]*commonmodels.ServiceModuleTarget, 0)
				if preBuild, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: serviceTmpl.BuildName, ProductName: productName}); err == nil {
					for _, target := range preBuild.Targets {
						if target.ServiceName != serviceName {
							updateTargets = append(updateTargets, target)
						}
					}
					_ = commonrepo.NewBuildColl().UpdateTargets(serviceTmpl.BuildName, productName, updateTargets)
				}
			}
		}
	}
	err := commonrepo.NewServiceColl().UpdateStatus(serviceName, productName, setting.ProductStatusDeleting)
	if err != nil {
		errMsg := fmt.Sprintf("[service.UpdateStatus] %s-%s error: %v", serviceName, serviceType, err)
		log.Error(errMsg)
		return e.ErrDeleteTemplate.AddDesc(errMsg)
	}
	if serviceType == setting.HelmDeployType {
		err = removeServiceFromRenderset(productName, productName, serviceName)
		if err != nil {
			log.Warnf("failed to update renderset: %s when deleting service: %s, err: %s", productName, serviceName, err.Error())
		}
		if err = fs.DeleteArchivedFileFromS3(serviceName, configbase.ObjectStorageServicePath(productName, serviceName), log); err != nil {
			log.Warnf("Failed to delete file %s, err: %s", serviceName, err)
		}
	}
	if productTempl, err := commonservice.GetProductTemplate(productName, log); err == nil {
		newServices := make([][]string, len(productTempl.Services))
		for i, services := range productTempl.Services {
			for _, service := range services {
				if service != serviceName {
					newServices[i] = append(newServices[i], service)
				}
			}
		}
		productTempl.Services = newServices
		err = templaterepo.NewProductColl().Update(productName, productTempl)
		if err != nil {
			log.Errorf("DeleteServiceTemplate Update %s error: %v", serviceName, err)
			return e.ErrDeleteTemplate.AddDesc(err.Error())
		}
		if serviceType == setting.HelmDeployType && productTempl.OnboardingStatus != 0 {
			envNames := []string{"dev", "qa"}
			for _, envName := range envNames {
				rendersetName := commonservice.GetProductEnvNamespace(envName, productName, "")
				err := removeServiceFromRenderset(productName, rendersetName, serviceName)
				if err != nil {
					log.Warnf("failed to update renderset: %s when deleting service: %s, err: %s", rendersetName, serviceName, err.Error())
				}
			}
		}
	}
	commonservice.DeleteServiceWebhookByName(serviceName, productName, log)
	return nil
}
func removeServiceFromRenderset(productName, renderName, serviceName string) error {
	renderOpt := &commonrepo.RenderSetFindOption{Name: renderName}
	if rs, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil {
		chartInfos := make([]*templatemodels.RenderChart, 0)
		for _, chartInfo := range rs.ChartInfos {
			if chartInfo.ServiceName == serviceName {
				continue
			}
			chartInfos = append(chartInfos, chartInfo)
		}
		rs.ChartInfos = chartInfos
		err = commonrepo.NewRenderSetColl().Update(rs)
		if err != nil {
			return err
		}
	}
	return nil
}
func ListServicePort(serviceName, serviceType, productName, excludeStatus string, revision int64, log *zap.SugaredLogger) ([]int, error) {
	servicePorts := make([]int, 0)
	opt := &commonrepo.ServiceFindOption{
		ServiceName:   serviceName,
		Type:          serviceType,
		Revision:      revision,
		ProductName:   productName,
		ExcludeStatus: excludeStatus,
	}
	resp, err := commonrepo.NewServiceColl().Find(opt)
	if err != nil {
		errMsg := fmt.Sprintf("[ServiceTmpl.Find] %s error: %v", serviceName, err)
		log.Error(errMsg)
		return servicePorts, e.ErrGetTemplate.AddDesc(errMsg)
	}
	yamlNew := strings.Replace(resp.Yaml, " {{", " '{{", -1)
	yamlNew = strings.Replace(yamlNew, "}}\n", "}}'\n", -1)
	yamls := strings.Split(yamlNew, "---")
	for _, yamlStr := range yamls {
		data, err := yaml.YAMLToJSON([]byte(yamlStr))
		if err != nil {
			log.Errorf("convert yaml to json failed, yaml:%s, err:%v", yamlStr, err)
			continue
		}
		yamlPreview := YamlPreviewForPorts{}
		err = json.Unmarshal(data, &yamlPreview)
		if err != nil {
			log.Errorf("unmarshal yaml data failed, yaml:%s, err:%v", yamlStr, err)
			continue
		}
		if yamlPreview.Kind != "Service" || yamlPreview.Spec == nil {
			continue
		}
		for _, port := range yamlPreview.Spec.Ports {
			servicePorts = append(servicePorts, port.Port)
		}
	}
	return servicePorts, nil
}
type svcInfo struct {
	ServiceName string `json:"service_name"`
	ProductName string `json:"product_name"`
}