diff --git a/pkg/microservice/aslan/core/common/service/kube/render.go b/pkg/microservice/aslan/core/common/service/kube/render.go index 2774aae33b..031f0f108c 100644 --- a/pkg/microservice/aslan/core/common/service/kube/render.go +++ b/pkg/microservice/aslan/core/common/service/kube/render.go @@ -1005,6 +1005,7 @@ func GenerateRenderedYaml(option *GeneSvcYamlOption) (string, int, []*WorkloadRe latestSvcTemplate.Containers = commonutil.FoldManualModulesInto(latestSvcTemplate.Containers, manualContainers) mergedContainers := mergeContainers(curContainers, latestSvcTemplate.Containers, svcContainersInProduct, option.Containers) + fullRenderedYaml, workloadResource, err := ReplaceWorkloadImages(fullRenderedYaml, mergedContainers) if err != nil { return "", 0, nil, fmt.Errorf("failed to replace workload images: %v", err) @@ -1105,6 +1106,14 @@ func RenderServiceYaml(originYaml, productName, serviceName string, svcRender *t // RenderEnvService renders service with particular revision and service vars in environment func RenderEnvService(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService) (yaml string, err error) { + return renderEnvService(prod, serviceRender, service, true) +} + +func RenderEnvServiceNotSetImages(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService) (yaml string, err error) { + return renderEnvService(prod, serviceRender, service, false) +} + +func renderEnvService(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService, setImages bool) (yaml string, err error) { opt := &commonrepo.ServiceFindOption{ ServiceName: service.ServiceName, ProductName: service.ProductName, @@ -1121,10 +1130,18 @@ func RenderEnvService(prod *commonmodels.Product, serviceRender *template.Servic if err != nil { return "", err } - return RenderEnvServiceWithTempl(prod, serviceRender, service, svcTmpl, cluster.Name) + return renderEnvServiceWithTempl(prod, serviceRender, service, svcTmpl, cluster.Name, setImages) } func RenderEnvServiceWithTempl(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService, svcTmpl *commonmodels.Service, clusterName string) (yaml string, err error) { + return renderEnvServiceWithTempl(prod, serviceRender, service, svcTmpl, clusterName, true) +} + +func RenderEnvServiceWithTemplNotSetImages(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService, svcTmpl *commonmodels.Service, clusterName string) (yaml string, err error) { + return renderEnvServiceWithTempl(prod, serviceRender, service, svcTmpl, clusterName, false) +} + +func renderEnvServiceWithTempl(prod *commonmodels.Product, serviceRender *template.ServiceRender, service *commonmodels.ProductService, svcTmpl *commonmodels.Service, clusterName string, setImages bool) (yaml string, err error) { // Note only the keys in TemplateService.ServiceVar can work parsedYaml, err := RenderServiceYaml(svcTmpl.Yaml, prod.ProductName, svcTmpl.ServiceName, serviceRender) if err != nil { @@ -1133,33 +1150,36 @@ func RenderEnvServiceWithTempl(prod *commonmodels.Product, serviceRender *templa } parsedYaml = ParseSysKeys(prod.Namespace, prod.EnvName, prod.ProductName, service.ServiceName, clusterName, parsedYaml) - // Fold manual modules into service.Containers before substitution. - // - // service.Containers nominally reflects the env's runtime snapshot, but - // callers like buildPreviewCandidateOverrides clone a ProductService and - // only override Revision - Containers still reflect the OLD revision's - // state. When the new revision's YAML carries $-image$ for a - // module that wasn't in the env before, substitution fails without this - // re-merge. - // - // For "true env snapshot" callers (e.g. genuinely rendering what's in - // env), the fold is a no-op when env state is consistent. The only - // observable difference is in the candidate-render path, which is what - // we want. - manualContainers, err := repository.ListManualServiceModules(context.Background(), prod.ProductName, service.ServiceName, prod.Production) - if err != nil { - return "", fmt.Errorf("failed to list manual service modules: %v", err) - } - allContainers := commonutil.FoldManualModulesInto(service.Containers, manualContainers) + if setImages { + // Fold manual modules into service.Containers before substitution. + // + // service.Containers nominally reflects the env's runtime snapshot, but + // callers like buildPreviewCandidateOverrides clone a ProductService and + // only override Revision - Containers still reflect the OLD revision's + // state. When the new revision's YAML carries $-image$ for a + // module that wasn't in the env before, substitution fails without this + // re-merge. + // + // For "true env snapshot" callers (e.g. genuinely rendering what's in + // env), the fold is a no-op when env state is consistent. The only + // observable difference is in the candidate-render path, which is what + // we want. + manualContainers, err := repository.ListManualServiceModules(context.Background(), prod.ProductName, service.ServiceName, prod.Production) + if err != nil { + return "", fmt.Errorf("failed to list manual service modules: %v", err) + } + allContainers := commonutil.FoldManualModulesInto(service.Containers, manualContainers) - parsedYaml, _, err = ReplaceWorkloadImages(parsedYaml, allContainers) - if err != nil { - return "", err - } - parsedYaml, err = ParseModuleImageKeys(parsedYaml, allContainers, false) - if err != nil { - return "", err + parsedYaml, _, err = ReplaceWorkloadImages(parsedYaml, allContainers) + if err != nil { + return "", err + } + parsedYaml, err = ParseModuleImageKeys(parsedYaml, allContainers, false) + if err != nil { + return "", err + } } + return ApplyReplicaOverrides(parsedYaml, service.WorkLoads) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ea035bb303..b223eb1f0c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -3030,15 +3030,30 @@ func upsertService(env *commonmodels.Product, newService *commonmodels.ProductSe return nil, nil } - parsedYaml, err := kube.RenderEnvService(env, newService.GetServiceRender(), newService) - if err != nil { - log.Errorf("Failed to render newService %s, error: %v", newService.ServiceName, err) - errList = multierror.Append(errList, fmt.Errorf("newService template %s error: %v", newService.ServiceName, err)) - return nil, errList - } + var err error + var parsedYaml string + + if prevSvc != nil { + parsedYaml, err = kube.RenderEnvService(env, newService.GetServiceRender(), newService) + if err != nil { + log.Errorf("Failed to render newService %s, error: %v", newService.ServiceName, err) + errList = multierror.Append(errList, fmt.Errorf("newService template %s error: %v", newService.ServiceName, err)) + return nil, errList + } + } else { + // When prevSvc is nil, it means this is a new service. + // So we need to render the service yaml without setting the images. + parsedYaml, err = kube.RenderEnvServiceNotSetImages(env, newService.GetServiceRender(), newService) + if err != nil { + return nil, err + } + + fakeTemplateSvc := &commonmodels.Service{ + ServiceName: newService.ServiceName, + ProductName: newService.ServiceName, + KubeYamls: util.SplitYaml(parsedYaml), + } - if prevSvc == nil { - fakeTemplateSvc := &commonmodels.Service{ServiceName: newService.ServiceName, ProductName: newService.ServiceName, KubeYamls: util.SplitYaml(parsedYaml)} commonutil.SetCurrentContainerImages(fakeTemplateSvc) // Merge instead of overwrite. fakeTemplateSvc.Containers is the // auto set re-parsed from the just-rendered YAML — authoritative diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 4090d7a526..37acb69c3c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -290,6 +290,7 @@ func updateK8sProduct(exitedProd *commonmodels.Product, user, requestID string, log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + updateProd.Production = exitedProd.Production updateProd.GlobalVariables = exitedProd.GlobalVariables updateProd.DefaultValues = exitedProd.DefaultValues