diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index af2aafb7b3..060c0e500f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -223,6 +223,7 @@ type JobTaskDeploySpec struct { KeyVals []*ServiceKeyVal `bson:"key_vals" json:"key_vals" yaml:"key_vals"` // deprecated since 1.18.0 VariableKVs []*commontypes.RenderVariableKV `bson:"variable_kvs" json:"variable_kvs" yaml:"variable_kvs"` // new since 1.18.0, only used for k8s UpdateConfig bool `bson:"update_config" json:"update_config" yaml:"update_config"` + IsImportToDeploy bool `bson:"is_import_to_deploy" json:"is_import_to_deploy" yaml:"is_import_to_deploy"` YamlContent string `bson:"yaml_content" json:"yaml_content" yaml:"yaml_content"` ServiceAndImages []*DeployServiceModule `bson:"service_and_images" json:"service_and_images" yaml:"service_and_images"` ServiceType string `bson:"service_type" json:"service_type" yaml:"service_type"` @@ -234,7 +235,7 @@ type JobTaskDeploySpec struct { ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources" yaml:"replace_resources"` RelatedPodLabels []map[string]string `bson:"-" json:"-" yaml:"-"` // overrideResource is used to do a full yaml override instead of a 2-way merge patching for all the resources - OverrideResource bool `bson:"override_resource" json:"override_resource" yaml:"override_resource"` + OverrideResource bool `bson:"override_resource" json:"override_resource" yaml:"override_resource"` // for compatibility ServiceModule string `bson:"service_module" json:"service_module" yaml:"-"` Image string `bson:"image" json:"image" yaml:"-"` @@ -266,7 +267,7 @@ type JobTaskDeployRevertSpec struct { OverrideKVs string `bson:"override_kvs" json:"override_kvs" yaml:"override_kvs"` Revision int64 `bson:"revision" json:"revision" yaml:"revision"` RevisionCreateTime int64 `bson:"revision_create_time" json:"revision_create_time" yaml:"revision_create_time"` - OverrideResource bool `bson:"override_resource" json:"override_resource" yaml:"override_resource"` + OverrideResource bool `bson:"override_resource" json:"override_resource" yaml:"override_resource"` } type DeployServiceModule struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 427c1961b8..ee8530fab0 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -544,12 +544,13 @@ type DeployHelmChart struct { } type DeployBasicInfo struct { - ServiceName string `bson:"service_name" yaml:"service_name" json:"service_name"` - Modules []*DeployModuleInfo `bson:"modules" yaml:"modules" json:"modules"` - Deployed bool `bson:"deployed" yaml:"deployed" json:"deployed"` - AutoSync bool `bson:"-" yaml:"auto_sync" json:"auto_sync"` - UpdateConfig bool `bson:"update_config" yaml:"update_config" json:"update_config"` - Updatable bool `bson:"-" yaml:"updatable" json:"updatable"` + ServiceName string `bson:"service_name" yaml:"service_name" json:"service_name"` + DeployStrategy string `bson:"deploy_strategy" yaml:"deploy_strategy" json:"deploy_strategy"` + Modules []*DeployModuleInfo `bson:"modules" yaml:"modules" json:"modules"` + Deployed bool `bson:"deployed" yaml:"deployed" json:"deployed"` + AutoSync bool `bson:"-" yaml:"auto_sync" json:"auto_sync"` + UpdateConfig bool `bson:"update_config" yaml:"update_config" json:"update_config"` + Updatable bool `bson:"-" yaml:"updatable" json:"updatable"` } type DeployOptionInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/kube/render.go b/pkg/microservice/aslan/core/common/service/kube/render.go index 88418e5b42..e376358ba7 100644 --- a/pkg/microservice/aslan/core/common/service/kube/render.go +++ b/pkg/microservice/aslan/core/common/service/kube/render.go @@ -32,7 +32,7 @@ import ( yamlutil "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/cli-runtime/pkg/printers" "k8s.io/helm/pkg/releaseutil" - yaml "sigs.k8s.io/yaml/goyaml.v3" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/v2/pkg/microservice/aslan/config" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models" @@ -58,6 +58,7 @@ type GeneSvcYamlOption struct { EnvName string ServiceName string UpdateServiceRevision bool + IsImportToDeploy bool VariableYaml string VariableKVs []*commontypes.RenderVariableKV ReplicaOverrides []*commonmodels.WorkLoad @@ -400,20 +401,284 @@ func FetchCurrentAppliedYaml(option *GeneSvcYamlOption) (string, int, error) { return "", 0, errors.Wrapf(err, "failed to find service %s with revision %d", option.ServiceName, curProductSvc.Revision) } - fullRenderedYaml, err := RenderServiceYaml(prodSvcTemplate.Yaml, option.ProductName, option.ServiceName, curProductSvc.GetServiceRender()) + if option.IsImportToDeploy { + importedAllManifests, _, err := FetchImportedAllManifests(productInfo, prodSvcTemplate, curProductSvc.GetServiceRender()) + if err != nil { + return "", 0, err + } + return importedAllManifests, 0, err + } else { + fullRenderedYaml, err := RenderServiceYaml(prodSvcTemplate.Yaml, option.ProductName, option.ServiceName, curProductSvc.GetServiceRender()) + if err != nil { + return "", 0, err + } + fullRenderedYaml = ParseSysKeys(productInfo.Namespace, productInfo.EnvName, option.ProductName, option.ServiceName, fullRenderedYaml) + mergedContainers := mergeContainers(prodSvcTemplate.Containers, curProductSvc.Containers) + fullRenderedYaml, _, err = ReplaceWorkloadImages(fullRenderedYaml, mergedContainers) + if err != nil { + return "", 0, err + } + + replicaOverrides := resolveReplicaOverrides(curProductSvc.WorkLoads, option.ReplicaOverrides, option.IgnoreCurrentReplicaOverrides) + fullRenderedYaml, err = ApplyReplicaOverrides(fullRenderedYaml, replicaOverrides) + return fullRenderedYaml, 0, err + } +} + +func FetchImportedAllManifests(envInfo *models.Product, serviceTmp *models.Service, svcRender *template.ServiceRender) (string, []*WorkloadResource, error) { + fullRenderedYaml, err := RenderServiceYaml(serviceTmp.Yaml, envInfo.ProductName, serviceTmp.ServiceName, svcRender) if err != nil { - return "", 0, err + return "", nil, err } - fullRenderedYaml = ParseSysKeys(productInfo.Namespace, productInfo.EnvName, option.ProductName, option.ServiceName, fullRenderedYaml) - mergedContainers := mergeContainers(prodSvcTemplate.Containers, curProductSvc.Containers) - fullRenderedYaml, _, err = ReplaceWorkloadImages(fullRenderedYaml, mergedContainers) + fullRenderedYaml = ParseSysKeys(envInfo.Namespace, envInfo.EnvName, envInfo.ProductName, serviceTmp.ServiceName, fullRenderedYaml) + + manifests := util.SplitManifestsOrdered(fullRenderedYaml) + + kubeClient, err := clientmanager.NewKubeClientManager().GetControllerRuntimeClient(envInfo.ClusterID) if err != nil { - return "", 0, err + log.Errorf("cluster is not connected [%s]", envInfo.ClusterID) + return "", nil, errors.Wrapf(err, "cluster is not connected [%s]", envInfo.ClusterID) } - replicaOverrides := resolveReplicaOverrides(curProductSvc.WorkLoads, option.ReplicaOverrides, option.IgnoreCurrentReplicaOverrides) - fullRenderedYaml, err = ApplyReplicaOverrides(fullRenderedYaml, replicaOverrides) - return fullRenderedYaml, 0, err + clientset, err := clientmanager.NewKubeClientManager().GetKubernetesClientSet(envInfo.ClusterID) + if err != nil { + log.Errorf("get client set error: %v", err) + return "", nil, err + } + versionInfo, err := clientset.Discovery().ServerVersion() + if err != nil { + log.Errorf("get server version error: %v", err) + return "", nil, err + } + manifestArr := make([]string, 0) + workloadRes := make([]*WorkloadResource, 0) + + for _, item := range manifests { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(item)) + if err != nil { + return "", nil, errors.Wrapf(err, "failed to decode yaml %s", item) + } + kind := u.GetKind() + name := u.GetName() + + var bs []byte + var exist bool + isWorkload := false + + switch kind { + case setting.Deployment: + bs, exist, err = getter.GetDeploymentYamlFormat(envInfo.Namespace, name, kubeClient) + isWorkload = true + case setting.StatefulSet: + bs, exist, err = getter.GetStatefulSetYamlFormat(envInfo.Namespace, name, kubeClient) + isWorkload = true + case setting.CronJob: + bs, exist, err = getter.GetCronJobYamlFormat(envInfo.Namespace, name, kubeClient, kubeclient.VersionLessThan121(versionInfo)) + isWorkload = true + case setting.Job: + bs, exist, err = getter.GetJobYaml(envInfo.Namespace, name, kubeClient) + isWorkload = true + case setting.Service: + bs, exist, err = getter.GetServiceYamlFormat(envInfo.Namespace, name, kubeClient) + case setting.ConfigMap: + bs, exist, err = getter.GetConfigMapYamlFormat(envInfo.Namespace, name, kubeClient) + case setting.Secret: + bs, exist, err = getter.GetSecretYamlFormat(envInfo.Namespace, name, kubeClient) + case setting.Ingress: + bs, exist, err = getter.GetIngressYamlFormat(envInfo.Namespace, name, kubeClient) + case setting.PersistentVolumeClaim: + bs, exist, err = getter.GetPVCYamlFormat(envInfo.Namespace, name, kubeClient) + default: + log.Warnf("unsupported resource kind %s/%s, skipping", kind, name) + continue + } + + if err != nil { + return "", nil, errors.Wrapf(err, "failed to get %s %s", kind, name) + } + if !exist { + return "", nil, errors.Errorf("%s %s not found", kind, name) + } + if isWorkload { + workloadRes = append(workloadRes, &WorkloadResource{Type: kind, Name: name}) + } + + cleaned, err := cleanupClusterResource(bs) + if err != nil { + return "", nil, errors.Wrapf(err, "failed to cleanup %s %s", kind, name) + } + manifestArr = append(manifestArr, cleaned) + } + + return util.JoinYamls(manifestArr), workloadRes, nil +} + +var serverGeneratedAnnotations = []string{ + "kubectl.kubernetes.io/last-applied-configuration", + "deployment.kubernetes.io/revision", + "deprecated.daemonset.template.generation", + "pv.kubernetes.io/bind-completed", + "pv.kubernetes.io/bound-by-controller", +} + +func cleanupClusterResource(yamlData []byte) (string, error) { + obj, err := serializer.NewDecoder().YamlToUnstructured(yamlData) + if err != nil { + return "", err + } + + delete(obj.Object, "status") + + if metadata, ok := obj.Object["metadata"].(map[string]interface{}); ok { + delete(metadata, "managedFields") + delete(metadata, "resourceVersion") + delete(metadata, "uid") + delete(metadata, "selfLink") + delete(metadata, "creationTimestamp") + delete(metadata, "generation") + } + + annotations := obj.GetAnnotations() + if annotations != nil { + for _, key := range serverGeneratedAnnotations { + delete(annotations, key) + } + if len(annotations) == 0 { + obj.SetAnnotations(nil) + } else { + obj.SetAnnotations(annotations) + } + } + + cleanupDefaultedSpec(obj.Object, obj.GetKind()) + + resp, err := yaml.Marshal(obj.Object) + if err != nil { + return "", err + } + return string(resp), nil +} + +func cleanupDefaultedSpec(objMap map[string]interface{}, kind string) { + cleanupPodTemplate(objMap, "spec", "template") + cleanupPodTemplate(objMap, "spec", "jobTemplate", "spec", "template") + + if jobTmpl := nestedMap(objMap, "spec", "jobTemplate"); jobTmpl != nil { + if metadata, ok := jobTmpl["metadata"].(map[string]interface{}); ok { + delete(metadata, "creationTimestamp") + } + } + + switch kind { + case setting.Service: + cleanupServiceDefaults(objMap) + case setting.PersistentVolumeClaim: + if spec := nestedMap(objMap, "spec"); spec != nil { + deleteIfDefaultStr(spec, "volumeMode", "Filesystem") + } + case setting.Secret: + deleteIfDefaultStr(objMap, "type", "Opaque") + } +} + +func cleanupPodTemplate(objMap map[string]interface{}, path ...string) { + tmpl := nestedMap(objMap, path...) + if tmpl == nil { + return + } + + if metadata, ok := tmpl["metadata"].(map[string]interface{}); ok { + delete(metadata, "creationTimestamp") + } + + podSpec, ok := tmpl["spec"].(map[string]interface{}) + if !ok { + return + } + + deleteIfDefaultStr(podSpec, "dnsPolicy", "ClusterFirst") + deleteIfDefaultStr(podSpec, "schedulerName", "default-scheduler") + deleteIfDefaultBool(podSpec, "enableServiceLinks", true) + deleteEmptyMap(podSpec, "securityContext") + delete(podSpec, "serviceAccount") + + for _, key := range []string{"containers", "initContainers"} { + containers, ok := podSpec[key].([]interface{}) + if !ok { + continue + } + for _, c := range containers { + container, ok := c.(map[string]interface{}) + if !ok { + continue + } + deleteIfDefaultStr(container, "terminationMessagePath", "/dev/termination-log") + deleteIfDefaultStr(container, "terminationMessagePolicy", "File") + deleteEmptyMap(container, "resources") + if ports, ok := container["ports"].([]interface{}); ok { + for _, p := range ports { + if port, ok := p.(map[string]interface{}); ok { + deleteIfDefaultStr(port, "protocol", "TCP") + } + } + } + } + } +} + +func cleanupServiceDefaults(objMap map[string]interface{}) { + spec := nestedMap(objMap, "spec") + if spec == nil { + return + } + + if clusterIP, ok := spec["clusterIP"].(string); ok && clusterIP != "None" { + delete(spec, "clusterIP") + delete(spec, "clusterIPs") + } + + deleteIfDefaultStr(spec, "sessionAffinity", "None") + deleteIfDefaultStr(spec, "internalTrafficPolicy", "Cluster") + delete(spec, "ipFamilies") + delete(spec, "ipFamilyPolicy") + + if ports, ok := spec["ports"].([]interface{}); ok { + for _, p := range ports { + if port, ok := p.(map[string]interface{}); ok { + deleteIfDefaultStr(port, "protocol", "TCP") + } + } + } +} + +func nestedMap(obj map[string]interface{}, keys ...string) map[string]interface{} { + cur := obj + for _, k := range keys { + next, ok := cur[k].(map[string]interface{}) + if !ok { + return nil + } + cur = next + } + return cur +} + +func deleteIfDefaultStr(m map[string]interface{}, key, defaultVal string) { + if v, ok := m[key].(string); ok && v == defaultVal { + delete(m, key) + } +} + +func deleteIfDefaultBool(m map[string]interface{}, key string, defaultVal bool) { + if v, ok := m[key].(bool); ok && v == defaultVal { + delete(m, key) + } +} + +func deleteEmptyMap(m map[string]interface{}, key string) { + if v, ok := m[key].(map[string]interface{}); ok && len(v) == 0 { + delete(m, key) + } } func FetchImportedManifests(option *GeneSvcYamlOption, productInfo *models.Product, serviceTmp *models.Service, svcRender *template.ServiceRender) (string, []*WorkloadResource, error) { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_deploy.go index 48a7681b77..5d3210ba4f 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_deploy.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_deploy.go @@ -187,9 +187,18 @@ func (c *DeployJobCtl) run(ctx context.Context) error { deployContentStr += contentStr + ", " } } - deployContentStr = strings.TrimSuffix(deployContentStr, ", ") + isImportToDeploy := false + deployContentStr = strings.TrimSuffix(deployContentStr, ", ") logContent := fmt.Sprintf("Start to deploy k8s yaml service %s, env: %s, namespace: %s, deploy contents: %s", c.jobTaskSpec.ServiceName, c.jobTaskSpec.Env, c.namespace, deployContentStr) + + if env.ServiceDeployStrategy[c.jobTaskSpec.ServiceName] == setting.ServiceDeployStrategyImport && (slices.Contains(c.jobTaskSpec.DeployContents, config.DeployVars) || slices.Contains(c.jobTaskSpec.DeployContents, config.DeployConfig)) { + isImportToDeploy = true + c.jobTaskSpec.IsImportToDeploy = true + logContent += fmt.Sprintf(", isImportToDeploy: %v", true) + c.logger.Infof("Deploy job: deploy service %s from import to deploy, override resource", c.jobTaskSpec.ServiceName) + } + logManager.SaveJobLog(logContent) var updateRevision bool @@ -220,9 +229,10 @@ func (c *DeployJobCtl) run(ctx context.Context) error { } currentYaml, _, err := kube.FetchCurrentAppliedYaml(&kube.GeneSvcYamlOption{ - ProductName: env.ProductName, - EnvName: c.jobTaskSpec.Env, - ServiceName: c.jobTaskSpec.ServiceName, + ProductName: env.ProductName, + EnvName: c.jobTaskSpec.Env, + ServiceName: c.jobTaskSpec.ServiceName, + IsImportToDeploy: isImportToDeploy, }) if err != nil { @@ -279,6 +289,10 @@ func (c *DeployJobCtl) run(ctx context.Context) error { c.jobTaskSpec.OriginRevision = latestRevision c.ack() + if env.ServiceDeployStrategy[c.jobTaskSpec.ServiceName] == setting.ServiceDeployStrategyImport && (slices.Contains(c.jobTaskSpec.DeployContents, config.DeployVars) || slices.Contains(c.jobTaskSpec.DeployContents, config.DeployConfig)) { + c.jobTaskSpec.OverrideResource = true + } + // if not only deploy image, we will redeploy service if err := c.updateSystemService(env, currentYaml, updatedYaml, c.jobTaskSpec.VariableKVs, revision, containers, candidateReplicaOverrides, updateRevision, c.jobTaskSpec.ServiceName, c.jobTaskSpec.OverrideResource); err != nil { logError(c.job, err.Error(), c.logger) diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_env.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_env.go index ad6a2861d7..f886d87023 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_env.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_env.go @@ -78,9 +78,10 @@ func mergeContainers(currentContainer []*models.Container, newContainers ...[]*m } func variableYamlNil(variableYaml string) bool { - if len(variableYaml) == 0 { + if len(variableYaml) == 0 || variableYaml == "{}" || variableYaml == "{}\n" { return true } + kvMap, _ := converter.YamlToFlatMap([]byte(variableYaml)) return len(kvMap) == 0 } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 0d1299baba..578229a405 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -183,6 +183,11 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return e.ErrUpdateEnv.AddDesc(err.Error()) } + if prodinfo.ServiceDeployStrategy[args.ServiceName] == setting.ServiceDeployStrategyImport && (args.UpdateServiceTmpl || len(args.ServiceRev.VariableKVs) > 0) { + args.OverrideResource = true + log.Infof("Update service %s from import to deploy, override resource", args.ServiceName) + } + // resource will not be applied if service yaml is not changed previewArg := &PreviewServiceArgs{ ProductName: prodinfo.ProductName, diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index feeb6af977..7ce7516398 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/repository" commontypes "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/types" + commonutil "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/v2/pkg/setting" e "github.com/koderover/zadig/v2/pkg/tool/errors" "github.com/koderover/zadig/v2/pkg/tool/log" @@ -243,6 +244,8 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar ServiceRevisions: make([]*commonrepo.ServiceRevision, 0), } for _, productSvc := range serviceMap { + productSvc.DeployStrategy = commonutil.GetServiceDeployStrategy(productSvc.ServiceName, prod.ServiceDeployStrategy) + listOpt.ServiceRevisions = append(listOpt.ServiceRevisions, &commonrepo.ServiceRevision{ ServiceName: productSvc.ServiceName, Revision: productSvc.Revision, diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index a5186c3dfd..8e40402b14 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -19,6 +19,7 @@ package service import ( "context" "fmt" + "slices" "github.com/koderover/zadig/v2/pkg/tool/clientmanager" "github.com/pkg/errors" @@ -313,6 +314,14 @@ func FetchServiceYaml(productName, envName, serviceName string, _ *zap.SugaredLo } func PreviewService(args *PreviewServiceArgs, _ *zap.SugaredLogger) (*SvcDiffResult, error) { + envInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return nil, e.ErrPreviewYaml.AddErr(err) + } + newVariableYaml, err := commontypes.RenderVariableKVToYaml(args.VariableKVs, true) if err != nil { return nil, e.ErrPreviewYaml.AddErr(err) @@ -324,11 +333,41 @@ func PreviewService(args *PreviewServiceArgs, _ *zap.SugaredLogger) (*SvcDiffRes Latest: TmplYaml{}, } - curYaml, _, err := kube.FetchCurrentAppliedYaml(&kube.GeneSvcYamlOption{ + curYaml := "" + isImportToDeploy := false + if envInfo.ServiceDeployStrategy[args.ServiceName] == setting.ServiceDeployStrategyImport { + // is imported service + if len(args.DeployContents) > 0 { + // is workflow + if len(args.DeployContents) == 1 && slices.Contains(args.DeployContents, config.DeployImage) { + // only update images + envSvc := envInfo.GetServiceMap()[args.ServiceName] + if envSvc == nil { + return nil, e.ErrPreviewYaml.AddErr(fmt.Errorf("service %s not found in environment", args.ServiceName)) + } + for _, container := range envSvc.Containers { + ret.Current.Yaml += container.Image + "\n" + } + for _, container := range args.ServiceModules { + ret.Latest.Yaml += container.Image + "\n" + } + return ret, nil + } else if slices.Contains(args.DeployContents, config.DeployVars) || slices.Contains(args.DeployContents, config.DeployConfig) { + // set update variables or configuration + isImportToDeploy = true + } + } else { + // is environment + isImportToDeploy = true + } + } + + curYaml, _, err = kube.FetchCurrentAppliedYaml(&kube.GeneSvcYamlOption{ ProductName: args.ProductName, EnvName: args.EnvName, ServiceName: args.ServiceName, - UpdateServiceRevision: args.UpdateServiceRevision, + UpdateServiceRevision: false, + IsImportToDeploy: isImportToDeploy, }) if err != nil { curYaml = "" @@ -337,26 +376,7 @@ func PreviewService(args *PreviewServiceArgs, _ *zap.SugaredLogger) (*SvcDiffRes log.Errorf(ret.Error) } - // for situations only update images, replace images directly - if !args.UpdateServiceRevision && len(args.VariableKVs) == 0 { - latestYaml, _, err := kube.ReplaceWorkloadImages(curYaml, args.ServiceModules) - if err != nil { - return nil, e.ErrPreviewYaml.AddErr(err) - } - ret.Current.Yaml = curYaml - ret.Latest.Yaml = latestYaml - return ret, nil - } - - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: args.ProductName, - EnvName: args.EnvName, - }) - if err != nil { - return nil, e.ErrPreviewYaml.AddErr(err) - } - - candidateOverrides, err := buildPreviewCandidateOverrides(product, args.ServiceName, args.UpdateServiceRevision, args.VariableKVs) + candidateOverrides, err := buildPreviewCandidateOverrides(envInfo, args.ServiceName, args.UpdateServiceRevision, args.VariableKVs) if err != nil { return nil, e.ErrPreviewYaml.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 7b6f66d304..10fce0895d 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -83,6 +83,7 @@ type PreviewServiceArgs struct { UpdateServiceRevision bool `json:"update_service_revision"` ServiceModules []*commonmodels.Container `json:"service_modules"` VariableKVs []*commontypes.RenderVariableKV `json:"variable_kvs"` + DeployContents []config.DeployContent `json:"deploy_contents"` } type RestartScaleArgs struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/controller/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/controller/job/job_deploy.go index ddc1341033..7c2dca9662 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/controller/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/controller/job/job_deploy.go @@ -1087,11 +1087,12 @@ func generateDeployInfoForEnv(env, project string, production bool, configuredSe } svcBasicInfo := commonmodels.DeployBasicInfo{ - ServiceName: service.ServiceName, - Modules: modules, - Deployed: true, - AutoSync: service.GetServiceRender().GetAutoSync(), - Updatable: serviceGeneralInfoMap[service.ServiceName].Updatable, + ServiceName: service.ServiceName, + DeployStrategy: envInfo.ServiceDeployStrategy[service.ServiceName], + Modules: modules, + Deployed: true, + AutoSync: service.GetServiceRender().GetAutoSync(), + Updatable: serviceGeneralInfoMap[service.ServiceName].Updatable, } serviceVariableInfo := &commonmodels.DeployVariableInfo{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 682d7342cd..a90d2147a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -179,6 +179,7 @@ type ZadigDeployJobPreviewSpec struct { ServiceType string `bson:"service_type" json:"service_type"` DeployContents []config.DeployContent `bson:"deploy_contents" json:"deploy_contents"` SkipCheckRunStatus bool `bson:"skip_check_run_status" json:"skip_check_run_status"` + IsImportToDeploy bool `bson:"is_import_to_deploy" json:"is_import_to_deploy"` ServiceAndImages []*ServiceAndImage `bson:"service_and_images" json:"service_and_images"` YamlContent string `bson:"yaml_content" json:"yaml_content"` // UserSuppliedValue added since 1.18, the values that users gives. @@ -1366,6 +1367,12 @@ func RevertWorkflowTaskV4Job(ctx *internalhandler.Context, workflowName, jobName return fmt.Errorf("failed to decode nacos job spec, error: %s", err) } + if jobTaskSpec.IsImportToDeploy { + err = fmt.Errorf("original deploy job is import to deploy, can't revert") + log.Error(err) + return err + } + job.Reverted = true task.Reverted = true err = commonrepo.NewworkflowTaskv4Coll().Update(task.ID.Hex(), task) @@ -2720,6 +2727,7 @@ func jobsToJobPreviews(jobs []*commonmodels.JobTask, context map[string]string, spec.YamlContent = taskJobSpec.YamlContent spec.SkipCheckRunStatus = taskJobSpec.SkipCheckRunStatus spec.OriginRevision = taskJobSpec.OriginRevision + spec.IsImportToDeploy = taskJobSpec.IsImportToDeploy // for compatibility if taskJobSpec.ServiceModule != "" { spec.ServiceAndImages = append(spec.ServiceAndImages, &ServiceAndImage{ diff --git a/pkg/util/helm.go b/pkg/util/helm.go index 94fd6214bf..55c92a649e 100644 --- a/pkg/util/helm.go +++ b/pkg/util/helm.go @@ -20,10 +20,12 @@ import ( "fmt" "io/fs" "path/filepath" + "sort" "strings" "go.uber.org/zap" "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/releaseutil" "k8s.io/helm/pkg/strvals" "github.com/koderover/zadig/v2/pkg/setting" @@ -100,3 +102,17 @@ func ReadValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, er } return content, nil } + +func SplitManifestsOrdered(content string) []string { + res := make([]string, 0) + manifests := releaseutil.SplitManifests(content) + manifestKeys := make([]string, 0, len(manifests)) + for key := range manifests { + manifestKeys = append(manifestKeys, key) + } + sort.Sort(releaseutil.BySplitManifestsOrder(manifestKeys)) + for _, key := range manifestKeys { + res = append(res, manifests[key]) + } + return res +}