From eacdf2347f52b660d1452f391d1e23ee4187df8d Mon Sep 17 00:00:00 2001 From: Patrick Zhao Date: Tue, 14 Jan 2025 16:14:13 +0800 Subject: [PATCH] improve helm variables logic Signed-off-by: Patrick Zhao --- .../core/common/repository/models/product.go | 1 + .../repository/models/template/product.go | 1 + .../common/repository/models/workflow_v4.go | 2 + .../aslan/core/common/service/helm/helm.go | 154 ++++++++++++++++ .../aslan/core/common/service/kube/helm.go | 114 ++++++++++++ .../aslan/core/common/service/service.go | 2 + .../aslan/core/common/util/service.go | 5 +- .../core/environment/service/environment.go | 20 +- .../core/environment/service/renderset.go | 8 +- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_v4.go | 48 ++++- .../core/workflow/service/workflow/job/job.go | 1 + .../service/workflow/job/job_deploy.go | 3 + .../workflow/service/workflow/job/utils.go | 1 + .../workflow/service/workflow/workflow_v4.go | 172 +++++++++++++----- 15 files changed, 469 insertions(+), 64 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index c356b97345..9b97a7e99d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -179,6 +179,7 @@ type ProductService struct { RenderedYaml string `bson:"rendered_yaml,omitempty" json:"rendered_yaml,omitempty"` VariableYaml string `bson:"-" json:"variable_yaml,omitempty"` VariableKVs []*commontypes.RenderVariableKV `bson:"-" json:"variable_kvs,omitempty"` + ValuesYaml string `bson:"-" json:"values_yaml,omitempty"` Updatable bool `bson:"-" json:"updatable"` DeployStrategy string `bson:"-" json:"deploy_strategy"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 807625367e..204462f761 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -129,6 +129,7 @@ type CustomYaml struct { RenderVariableKVs []*commontypes.RenderVariableKV `bson:"render_variable_kvs" json:"render_variable_kvs"` Source string `bson:"source" json:"source"` AutoSync bool `bson:"auto_sync" json:"auto_sync"` + AutoSyncYaml string `bson:"auto_sync_yaml" json:"auto_sync_yaml"` SourceDetail interface{} `bson:"source_detail" json:"source_detail"` SourceID string `bson:"source_id" json:"source_id"` } 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 ba20d75070..4081f2b6f7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -441,7 +441,9 @@ type DeployServiceInfo struct { VariableConfigs []*DeployVariableConfig `bson:"variable_configs" yaml:"variable_configs,omitempty" json:"variable_configs,omitempty"` VariableKVs []*commontypes.RenderVariableKV `bson:"variable_kvs" yaml:"variable_kvs" json:"variable_kvs"` //LatestVariableKVs []*commontypes.RenderVariableKV `bson:"latest_variable_kvs" yaml:"latest_variable_kvs" json:"latest_variable_kvs"` + // helm 和 k8s 部署均使用该字段作为yaml格式的变量 VariableYaml string `bson:"variable_yaml" yaml:"variable_yaml" json:"variable_yaml"` + OverrideKVs string `bson:"override_kvs" yaml:"override_kvs"` // used for helm services, json-encoded string of kv value UpdateConfig bool `bson:"update_config" yaml:"update_config" json:"update_config"` Updatable bool `bson:"-" yaml:"updatable" json:"updatable"` Deployed bool `bson:"-" yaml:"deployed" json:"deployed"` diff --git a/pkg/microservice/aslan/core/common/service/helm/helm.go b/pkg/microservice/aslan/core/common/service/helm/helm.go index 38461846d9..99041ac171 100644 --- a/pkg/microservice/aslan/core/common/service/helm/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm/helm.go @@ -24,10 +24,12 @@ import ( "time" "go.uber.org/zap" + "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" commonmodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/mongodb/template" fsservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/fs" @@ -35,8 +37,10 @@ import ( "github.com/koderover/zadig/v2/pkg/setting" "github.com/koderover/zadig/v2/pkg/tool/cache" "github.com/koderover/zadig/v2/pkg/tool/crypto" + helmtool "github.com/koderover/zadig/v2/pkg/tool/helmclient" "github.com/koderover/zadig/v2/pkg/tool/log" "github.com/koderover/zadig/v2/pkg/tool/mongo" + "github.com/koderover/zadig/v2/pkg/util/converter" "github.com/pkg/errors" ) @@ -330,3 +334,153 @@ func UpdateServicesGroupInEnv(productName, envName string, index int, group []*m return mongo.CommitTransaction(session) } + +// GeneMergedValues generate values.yaml used to install or upgrade helm chart, like param in after option -f +// productSvc: contains current images info +// defaultValues: global values yaml +// images: images to be replaced +// fullValues: If fullValues is set to true, full values yaml content will be returned, this case is used to preview values when running workflows +func NewGeneMergedValues(productSvc *commonmodels.ProductService, defaultValues string) (string, string, error) { + envValuesYaml := productSvc.GetServiceRender().GetOverrideYaml() + overrideKVs := productSvc.GetServiceRender().OverrideValues + + valuesMap := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(envValuesYaml), &valuesMap) + if err != nil { + err = fmt.Errorf("Failed to unmarshall yaml, err %s", err) + return "", "", err + } + + flatValuesMap, err := converter.Flatten(valuesMap) + if err != nil { + return "", "", err + } + + // calc weather to add container image into values yaml + found := false + mergedContainers := []*commonmodels.Container{} + for _, container := range productSvc.Containers { + if container.ImagePath == nil { + return "", "", fmt.Errorf("failed to parse image for container:%s", container.Image) + } + + imageSearchRule := &templatemodels.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Namespace: container.ImagePath.Namespace, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imageUrl, err := commonutil.GeneImageURI(pattern, flatValuesMap) + if err != nil { + return "", "", fmt.Errorf("failed to get image url for container:%s", container.Image) + } + + name := commonutil.ExtractImageName(imageUrl) + + if container.ImageName == name { + // found image in values + found = true + } + + if !found { + // if not found corresponding service module image in values + // add container image into values + log.Debugf("not found service module %s's image in values, add it", container.ImageName) + mergedContainers = append(mergedContainers, container) + } + } + + serviceName := productSvc.ServiceName + imageValuesMaps := make([]map[string]interface{}, 0) + for _, mergedContainer := range mergedContainers { + log.Debugf("merged container: %+v", mergedContainer) + // prepare image replace info + replaceValuesMap, err := commonutil.AssignImageData(mergedContainer.Image, commonutil.GetValidMatchData(mergedContainer.ImagePath)) + if err != nil { + return "", "", fmt.Errorf("failed to pase image uri %s/%s, err %s", productSvc.ProductName, serviceName, err.Error()) + } + imageValuesMaps = append(imageValuesMaps, replaceValuesMap) + } + + log.Debugf("imageValuesMaps: %+v", imageValuesMaps) + // replace image into service's values.yaml + replacedEnvValuesYaml, err := commonutil.ReplaceImage(envValuesYaml, imageValuesMaps...) + if err != nil { + return "", "", fmt.Errorf("failed to replace image uri %s/%s, err %s", productSvc.ProductName, serviceName, err.Error()) + + } + if replacedEnvValuesYaml == "" { + return "", "", fmt.Errorf("failed to set new image uri into service's values.yaml %s/%s", productSvc.ProductName, serviceName) + } + + // merge override values and kvs into service's yaml + finalValuesYaml, err := helmtool.MergeOverrideValues("", defaultValues, replacedEnvValuesYaml, overrideKVs, nil) + if err != nil { + return "", "", fmt.Errorf("failed to merge override values, err: %s", err) + } + log.Debugf("replacedEnvValuesYaml: %s", replacedEnvValuesYaml) + log.Debugf("finalValuesYaml: %s", finalValuesYaml) + + // calc final containers' image + valuesMap = make(map[string]interface{}) + err = yaml.Unmarshal([]byte(finalValuesYaml), &valuesMap) + if err != nil { + err = fmt.Errorf("Failed to unmarshall yaml, err %s", err) + return "", "", err + } + + flatValuesMap, err = converter.Flatten(valuesMap) + if err != nil { + return "", "", err + } + for _, container := range productSvc.Containers { + if container.ImagePath == nil { + return "", "", fmt.Errorf("failed to parse image for container:%s", container.Image) + } + + imageSearchRule := &templatemodels.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Namespace: container.ImagePath.Namespace, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imageUrl, err := commonutil.GeneImageURI(pattern, flatValuesMap) + if err != nil { + return "", "", fmt.Errorf("failed to get image url for container:%s", container.Image) + } + log.Debugf("final image url: %s", imageUrl) + container.Image = imageUrl + } + + return finalValuesYaml, replacedEnvValuesYaml, nil +} + +func GeneFullValues(serviceValuesYaml, envValuesYaml string) (string, error) { + finalValuesYaml, err := helmtool.MergeOverrideValues(serviceValuesYaml, "", envValuesYaml, "", nil) + if err != nil { + return "", fmt.Errorf("failed to merge override values, err: %s", err) + } + + return finalValuesYaml, nil +} + +func convertImagePathToPatterns(container *commonmodels.Container) []map[string]string { + patterns := make([]map[string]string, 0) + if container.ImagePath != nil { + if container.ImagePath.Repo != "" { + patterns = append(patterns, map[string]string{setting.PathSearchComponentRepo: container.ImagePath.Repo}) + } + if container.ImagePath.Namespace != "" { + patterns = append(patterns, map[string]string{setting.PathSearchComponentNamespace: container.ImagePath.Namespace}) + } + if container.ImagePath.Image != "" { + patterns = append(patterns, map[string]string{setting.PathSearchComponentImage: container.ImagePath.Image}) + } + if container.ImagePath.Tag != "" { + patterns = append(patterns, map[string]string{setting.PathSearchComponentTag: container.ImagePath.Tag}) + } + } + return patterns +} diff --git a/pkg/microservice/aslan/core/common/service/kube/helm.go b/pkg/microservice/aslan/core/common/service/kube/helm.go index 61b81a9e60..95a169072a 100644 --- a/pkg/microservice/aslan/core/common/service/kube/helm.go +++ b/pkg/microservice/aslan/core/common/service/kube/helm.go @@ -143,6 +143,120 @@ func InstallOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo return err } +// // GeneMergedValues generate values.yaml used to install or upgrade helm chart, like param in after option -f +// // productSvc: contains current images info +// // defaultValues: global values yaml +// // images: images to be replaced +// // fullValues: If fullValues is set to true, full values yaml content will be returned, this case is used to preview values when running workflows +// func NewGeneMergedValues(templateSvc *commonmodels.Service, productSvc *commonmodels.ProductService, defaultValues string, fullValues bool) (string, string, error) { +// serviceValuesYaml := templateSvc.HelmChart.ValuesYaml +// defaultValues = defaultValues +// envValuesYaml := productSvc.GetServiceRender().GetOverrideYaml() +// overrideKVs := productSvc.GetServiceRender().OverrideValues +// // images: deprecated +// fullValues = fullValues + +// valuesMap := make(map[string]interface{}) +// err := yaml.Unmarshal([]byte(envValuesYaml), &valuesMap) +// if err != nil { +// err = fmt.Errorf("Failed to unmarshall yaml, err %s", err) +// return "", "", err +// } + +// flatValuesMap, err := converter.Flatten(valuesMap) +// if err != nil { +// return "", "", err +// } + +// mergedContainers := []*commonmodels.Container{} +// for _, container := range productSvc.Containers { +// patterns := convertImagePathToPatterns(container) + +// matchedPath, err := yamlutil.SearchByPattern(flatValuesMap, patterns) +// if err != nil { +// return "", "", err +// } + +// found := false +// usedImagePath := sets.NewString() +// for _, searchResult := range matchedPath { +// uniquePath := commonutil.GenerateUniquePath(searchResult) +// if usedImagePath.Has(uniquePath) { +// continue +// } +// usedImagePath.Insert(uniquePath) +// imageUrl, err := commonutil.GeneImageURI(searchResult, flatValuesMap) +// if err != nil { +// return "", "", err +// } +// name := commonutil.ExtractImageName(imageUrl) + +// if container.ImageName == name { +// // found image in values +// found = true +// } +// } + +// if !found { +// // if not found corresponding service module image in values +// // add container image into values +// mergedContainers = append(mergedContainers, container) +// } +// } + +// serviceName := productSvc.ServiceName +// imageValuesMaps := make([]map[string]interface{}, 0) +// for _, mergedContainer := range mergedContainers { +// // prepare image replace info +// replaceValuesMap, err := commonutil.AssignImageData(mergedContainer.Image, commonutil.GetValidMatchData(mergedContainer.ImagePath)) +// if err != nil { +// return "", "", fmt.Errorf("failed to pase image uri %s/%s, err %s", productSvc.ProductName, serviceName, err.Error()) +// } +// imageValuesMaps = append(imageValuesMaps, replaceValuesMap) +// } + +// // replace image into service's values.yaml +// replacedEnvValuesYaml, err := commonutil.ReplaceImage(envValuesYaml, imageValuesMaps...) +// if err != nil { +// return "", "", fmt.Errorf("failed to replace image uri %s/%s, err %s", productSvc.ProductName, serviceName, err.Error()) + +// } +// if replacedEnvValuesYaml == "" { +// return "", "", fmt.Errorf("failed to set new image uri into service's values.yaml %s/%s", productSvc.ProductName, serviceName) +// } + +// baseValuesYaml := "" +// if fullValues { +// baseValuesYaml = serviceValuesYaml +// } + +// // merge override values and kvs into service's yaml +// finalValuesYaml, err := helmtool.MergeOverrideValues(baseValuesYaml, defaultValues, replacedEnvValuesYaml, overrideKVs, nil) +// if err != nil { +// return "", "", fmt.Errorf("failed to merge override values, err: %s", err) +// } +// return finalValuesYaml, replacedEnvValuesYaml, nil +// } + +// func convertImagePathToPatterns(container *commonmodels.Container) []map[string]string { +// patterns := make([]map[string]string, 0) +// if container.ImagePath != nil { +// if container.ImagePath.Repo != "" { +// patterns = append(patterns, map[string]string{setting.PathSearchComponentRepo: container.ImagePath.Repo}) +// } +// if container.ImagePath.Namespace != "" { +// patterns = append(patterns, map[string]string{setting.PathSearchComponentNamespace: container.ImagePath.Namespace}) +// } +// if container.ImagePath.Image != "" { +// patterns = append(patterns, map[string]string{setting.PathSearchComponentImage: container.ImagePath.Image}) +// } +// if container.ImagePath.Tag != "" { +// patterns = append(patterns, map[string]string{setting.PathSearchComponentTag: container.ImagePath.Tag}) +// } +// } +// return patterns +// } + // GeneMergedValues generate values.yaml used to install or upgrade helm chart, like param in after option -f // productSvc: contains current images info // svcRender: contains env values info, including service's values and env's override values diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 6b5d519614..4e39697f72 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -132,6 +132,7 @@ type EnvService struct { ServiceName string `json:"service_name"` ServiceModules []*commonmodels.Container `json:"service_modules"` VariableYaml string `json:"variable_yaml"` + OverrideKVs string `json:"override_kvs"` VariableKVs []*commontypes.RenderVariableKV `json:"variable_kvs"` LatestVariableYaml string `json:"latest_variable_yaml"` LatestVariableKVs []*commontypes.RenderVariableKV `json:"latest_variable_kvs"` @@ -1187,6 +1188,7 @@ func BuildServiceInfoInEnv(productInfo *commonmodels.Product, templateSvcs []*co } } else if deployType == setting.HelmDeployType { svc.VariableYaml = productInfo.GetSvcRender(serviceName).OverrideYaml.YamlContent + svc.OverrideKVs = productInfo.GetSvcRender(serviceName).OverrideValues } ret.Services = append(ret.Services, svc) diff --git a/pkg/microservice/aslan/core/common/util/service.go b/pkg/microservice/aslan/core/common/util/service.go index 2d0f6e0642..151329f946 100644 --- a/pkg/microservice/aslan/core/common/util/service.go +++ b/pkg/microservice/aslan/core/common/util/service.go @@ -355,7 +355,7 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s ret := make([]*commonmodels.Container, 0) usedImagePath := sets.NewString() for _, searchResult := range matchedPath { - uniquePath := generateUniquePath(searchResult) + uniquePath := GenerateUniquePath(searchResult) if usedImagePath.Has(uniquePath) { continue } @@ -388,6 +388,7 @@ func ParseImagesByRules(nested map[string]interface{}, matchRules []*templatemod } patterns = append(patterns, rule.GetSearchingPattern()) } + log.Debugf("nested: %v\n, patterns: %v", nested, patterns) return parseImagesByPattern(nested, patterns) } @@ -411,7 +412,7 @@ func GetPresetRules() []*templatemodels.ImageSearchingRule { return ret } -func generateUniquePath(pathData map[string]string) string { +func GenerateUniquePath(pathData map[string]string) string { keys := []string{setting.PathSearchComponentRepo, setting.PathSearchComponentNamespace, setting.PathSearchComponentImage, setting.PathSearchComponentTag} values := make([]string, 0) for _, key := range keys { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index fb86c570ad..6a74e7a65f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1548,7 +1548,7 @@ func GeneEstimatedValues(productName, envName, serviceOrReleaseName, scene, form images = append(images, container.Image) } - mergedValues, err = kube.GeneMergedValues(productSvc, productSvc.GetServiceRender(), productInfo.DefaultValues, images, true) + mergedValues, _, err = helmservice.NewGeneMergedValues(productSvc, productInfo.DefaultValues) if err != nil { return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) } @@ -1789,7 +1789,7 @@ func SyncHelmProductEnvironment(productName, envName, requestID string, log *zap updatedRCMap := make(map[string]*templatemodels.ServiceRender) - changed, defaultValues, err := SyncYamlFromSource(product.YamlData, product.DefaultValues) + changed, defaultValues, err := SyncYamlFromSource(product.YamlData, product.DefaultValues, product.DefaultValues) if err != nil { log.Errorf("failed to update default values of env %s:%s", product.ProductName, product.EnvName) return err @@ -1804,12 +1804,13 @@ func SyncHelmProductEnvironment(productName, envName, requestID string, log *zap if chartInfo.OverrideYaml == nil { continue } - changed, values, err := SyncYamlFromSource(chartInfo.OverrideYaml, chartInfo.OverrideYaml.YamlContent) + changed, values, err := SyncYamlFromSource(chartInfo.OverrideYaml, chartInfo.OverrideYaml.YamlContent, chartInfo.OverrideYaml.AutoSyncYaml) if err != nil { return err } if changed { chartInfo.OverrideYaml.YamlContent = values + chartInfo.OverrideYaml.AutoSyncYaml = values updatedRCMap[chartInfo.ServiceName] = chartInfo } } @@ -2755,13 +2756,13 @@ func buildInstallParam(defaultValues string, productInfo *commonmodels.Product, Revision: productSvc.Revision, ProductName: productName, } - serviceObj, err := repository.QueryTemplateService(opt, productInfo.Production) + templateSvc, err := repository.QueryTemplateService(opt, productInfo.Production) if err != nil { log.Errorf("failed to find service %s, err %s", productSvc.ServiceName, err.Error()) return nil, nil } - ret.ServiceObj = serviceObj - ret.ReleaseName = util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName) + ret.ServiceObj = templateSvc + ret.ReleaseName = util.GeneReleaseName(templateSvc.GetReleaseNaming(), templateSvc.ProductName, namespace, envName, templateSvc.ServiceName) } else { serviceObj := &commonmodels.Service{ ServiceName: renderChart.ReleaseName, @@ -2776,12 +2777,12 @@ func buildInstallParam(defaultValues string, productInfo *commonmodels.Product, ret.ReleaseName = renderChart.ReleaseName } - mergedValues, err := commonutil.GeneHelmMergedValues(productSvc, defaultValues, renderChart) + finalValues, _, err := helmservice.NewGeneMergedValues(productSvc, defaultValues) if err != nil { return ret, err } - ret.MergedValues = mergedValues + ret.MergedValues = finalValues ret.Production = productInfo.Production return ret, nil } @@ -3167,8 +3168,9 @@ func proceedHelmRelease(productResp *commonmodels.Product, helmClient *helmtool. return err } prodSvc.Render = chartInfo - installParamList = append(installParamList, param) prodSvc.UpdateTime = time.Now().Unix() + installParamList = append(installParamList, param) + } groupServiceErr := batchExecutorWithRetry(3, time.Millisecond*500, installParamList, handler, log) if groupServiceErr != nil { diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 524c396075..9f858c6425 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -83,7 +83,7 @@ func syncYamlFromVariableSet(yamlData *templatemodels.CustomYaml, curValue strin return true, variableSet.VariableYaml, nil } -func syncYamlFromGit(yamlData *templatemodels.CustomYaml, curValue string) (bool, string, error) { +func syncYamlFromGit(yamlData *templatemodels.CustomYaml, curValue string, originValue string) (bool, string, error) { if !fromGitRepo(yamlData.Source) { return false, "", nil } @@ -108,7 +108,7 @@ func syncYamlFromGit(yamlData *templatemodels.CustomYaml, curValue string) (bool if err != nil { return false, "", err } - equal, err := yamlutil.Equal(string(valuesYAML), curValue) + equal, err := yamlutil.Equal(string(valuesYAML), originValue) if err != nil || equal { return false, "", err } @@ -117,14 +117,14 @@ func syncYamlFromGit(yamlData *templatemodels.CustomYaml, curValue string) (bool // SyncYamlFromSource sync values.yaml from source // NOTE for git source currently only support gitHub and gitlab -func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (bool, string, error) { +func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string, originValue string) (bool, string, error) { if yamlData == nil || !yamlData.AutoSync { return false, "", nil } if yamlData.Source == setting.SourceFromVariableSet { return syncYamlFromVariableSet(yamlData, curValue) } - return syncYamlFromGit(yamlData, curValue) + return syncYamlFromGit(yamlData, curValue, originValue) } func GetDefaultValues(productName, envName string, production bool, log *zap.SugaredLogger) (*DefaultValuesResp, error) { diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 68fff25780..886faaf327 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -193,6 +193,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowV4.GET("/bluegreen/:envName/:serviceName", GetBlueGreenServiceK8sServiceYaml) workflowV4.GET("/jenkins/:id/:jobName", GetJenkinsJobParams) workflowV4.POST("/sql/validate", ValidateSQL) + workflowV4.POST("/deploy/mergeImage", HelmDeployJobMergeImage) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index 90f3658e47..c78feb0b80 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -542,7 +542,6 @@ func UpdateWebhookForWorkflowV4(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() if err != nil { - ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err) ctx.UnAuthorized = true return @@ -1450,3 +1449,50 @@ func getBody(c *gin.Context) string { } return string(b) } + +type HelmDeployJobMergeImageRequest struct { + ServiceName string `json:"service_name"` + ValuesYaml string `json:"values_yaml"` + EnvName string `json:"env_name"` + IsProduction bool `json:"production"` + UpdateServiceRevision bool `json:"update_service_revision"` + ServiceModules []*ModuleAndImage `json:"service_modules"` +} + +// @Summary 工作流Helm部署任务合并镜像到ValuesYaml +// @Description +// @Tags workflow +// @Accept json +// @Produce json +// @Param projectName query string true "项目名称" +// @Param body body HelmDeployJobMergeImageRequest true "body" +// @Success 200 {object} workflow.HelmDeployJobMergeImageResponse +// @Router /api/aslan/workflow/v4/deploy/mergeImage [post] +func HelmDeployJobMergeImage(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 + } + + req := new(HelmDeployJobMergeImageRequest) + if err := c.ShouldBindJSON(req); err != nil { + ctx.RespErr = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.RespErr = e.ErrInvalidParam.AddDesc("projectName is required") + return + } + + images := make([]string, 0) + for _, imageInfos := range req.ServiceModules { + images = append(images, imageInfos.Image) + } + ctx.Resp, ctx.RespErr = workflow.HelmDeployJobMergeImage(ctx, projectName, req.EnvName, req.ServiceName, req.ValuesYaml, images, req.IsProduction, req.UpdateServiceRevision) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index 73d59079b0..184b2941b6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -63,6 +63,7 @@ type JobCtl interface { MergeArgs(args *commonmodels.Job) error // UpdateWithLatestSetting update the current workflow arguments with the latest workflow settings. // it will also calculate if the user's args is still valid, returning error if it is invalid. + // Used in release plan and workflow trigger UpdateWithLatestSetting() error LintJob() error } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 1ea208f951..6c35af1ed4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -292,6 +292,7 @@ func (j *DeployJob) SetPreset() error { VariableKVs: svc.VariableKVs, // LatestVariableKVs: svc.LatestVariableKVs, VariableYaml: svc.VariableYaml, + OverrideKVs: svc.OverrideKVs, UpdateConfig: svc.UpdateConfig, Updatable: svc.Updatable, Deployed: svc.Deployed, @@ -528,6 +529,7 @@ func (j *DeployJob) UpdateWithLatestSetting() error { // } //} + // @todo 处理合并helm变量,需要合并values和kvs mergedValues, err := helmtool.MergeOverrideValues("", service.VariableYaml, userSvc.VariableYaml, "", make([]*helmtool.KV, 0)) if err != nil { return fmt.Errorf("failed to merge helm values, error: %s", err) @@ -709,6 +711,7 @@ func generateEnvDeployServiceInfo(env, project string, spec *commonmodels.ZadigD // VariableKVs: kvs, // LatestVariableKVs: svcInfo.LatestVariableKVs, VariableYaml: service.GetServiceRender().OverrideYaml.YamlContent, + OverrideKVs: service.GetServiceRender().OverrideValues, UpdateConfig: updateConfig, Updatable: svcInfo.Updatable, Deployed: true, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/utils.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/utils.go index 9f084cd604..e060668e74 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/utils.go @@ -52,6 +52,7 @@ func FilterServiceVars(serviceName string, deployContents []config.DeployContent } service.VariableYaml = serviceEnv.VariableYaml + service.OverrideKVs = serviceEnv.OverrideKVs service.ServiceName = serviceName service.Updatable = serviceEnv.Updatable service.UpdateConfig = defaultUpdateConfig diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 84581c04ca..f336ed823f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -57,6 +57,7 @@ import ( templaterepo "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/collaboration" + helmservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/helm" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/kube" larkservice "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/lark" "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/service/repository" @@ -2370,81 +2371,156 @@ func CompareHelmServiceYamlInEnv(serviceName, variableYaml, envName, projectName currentYaml = resp.ValuesYaml } - param := &kube.ResourceApplyParam{ - ProductInfo: prod, - ServiceName: serviceName, - // no image preview available in this api - Images: images, - Uninstall: false, - UpdateServiceRevision: updateServiceRevision, - Timeout: setting.DeployTimeout, + productService, err := genNewEnvService(prod, serviceName, updateServiceRevision) + if err != nil { + return nil, err } - curProdSvcRevision := func() int64 { - if prod.GetServiceMap()[serviceName] != nil { - return prod.GetServiceMap()[serviceName].Revision - } - return 0 - }() + yamlContent, _, err := helmservice.NewGeneMergedValues(productService, prod.DefaultValues) + if err != nil { + log.Errorf("failed to generate merged values.yaml, err: %s", err) + return nil, err + } - productService, _, err := kube.PrepareHelmServiceData(param) + fullValuesYaml, err := helmservice.GeneFullValues(productService.ValuesYaml, yamlContent) if err != nil { - log.Errorf("prepare helm service data error: %v", err) + return nil, fmt.Errorf("failed to generate full values yaml, err: %s", err) + } + + // re-marshal it into string to make sure indentation is right + tmp := make(map[string]interface{}) + if err := yaml.Unmarshal([]byte(fullValuesYaml), &tmp); err != nil { + log.Errorf("failed to unmarshal latest yaml content, err: %s", err) + return nil, err + } + latestYamlContent, err := yaml.Marshal(tmp) + if err != nil { + log.Errorf("failed to marshal latest yaml content, err: %s", err) return nil, err } + return &GetHelmValuesDifferenceResp{ + Current: currentYaml, + Latest: string(latestYamlContent), + }, nil +} - if updateServiceRevision && curProdSvcRevision > int64(0) { - svcFindOption := &commonrepo.ServiceFindOption{ - ProductName: prod.ProductName, - ServiceName: serviceName, - } - latestSvc, err := repository.QueryTemplateService(svcFindOption, prod.Production) - if err != nil { - return nil, errors.Wrapf(err, "failed to find service %s/%d in product %s", serviceName, svcFindOption.Revision, prod.ProductName) +type HelmDeployJobMergeImageResponse struct { + Values string `json:"values"` +} + +func HelmDeployJobMergeImage(ctx *internalhandler.Context, projectName, envName, serviceName, valuesYaml string, images []string, isProduction, updateServiceRevision bool) (*HelmDeployJobMergeImageResponse, error) { + opt := &commonrepo.ProductFindOptions{Name: projectName, EnvName: envName, Production: &isProduction} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, fmt.Errorf("failed to find project: %s, err: %s", projectName, err) + } + + prodSvc, err := genNewEnvService(prod, serviceName, updateServiceRevision) + if err != nil { + return nil, err + } + + imageMap := make(map[string]string) + mergedContainers := []*commonmodels.Container{} + for _, image := range images { + imageMap[commonutil.ExtractImageName(image)] = image + } + for _, container := range prodSvc.Containers { + overrideImage, ok := imageMap[container.ImageName] + if ok { + container.Image = overrideImage + mergedContainers = append(mergedContainers, container) } + } - curUsedSvc, err := repository.QueryTemplateService(&commonrepo.ServiceFindOption{ - ProductName: prod.ProductName, - ServiceName: serviceName, - Revision: curProdSvcRevision, - }, prod.Production) + imageValuesMaps := make([]map[string]interface{}, 0) + for _, targetContainer := range mergedContainers { + // prepare image replace info + replaceValuesMap, err := commonutil.AssignImageData(targetContainer.Image, commonutil.GetValidMatchData(targetContainer.ImagePath)) if err != nil { - return nil, errors.Wrapf(err, "failed to find service %s/%d in product %s", serviceName, svcFindOption.Revision, prod.ProductName) + return nil, fmt.Errorf("failed to pase image uri %s/%s, err %s", projectName, serviceName, err.Error()) } - - containers := kube.CalculateContainer(productService, curUsedSvc, latestSvc.Containers, prod) - images = kube.MergeImages(containers, images) - param.Images = images + imageValuesMaps = append(imageValuesMaps, replaceValuesMap) } - chartInfo := productService.GetServiceRender() - param.VariableYaml = variableYaml - chartInfo.OverrideYaml.YamlContent = variableYaml - - yamlContent, err := kube.GeneMergedValues(productService, productService.GetServiceRender(), prod.DefaultValues, param.Images, true) + // replace image into service's values.yaml + mergedValuesYaml, err := commonutil.ReplaceImage(valuesYaml, imageValuesMaps...) if err != nil { - log.Errorf("failed to generate merged values.yaml, err: %s", err) - return nil, err + return nil, fmt.Errorf("failed to replace image uri %s/%s, err %s", projectName, serviceName, err.Error()) + } + // re-marshal it into string to make sure indentation is right tmp := make(map[string]interface{}) - if err := yaml.Unmarshal([]byte(yamlContent), &tmp); err != nil { + if err := yaml.Unmarshal([]byte(mergedValuesYaml), &tmp); err != nil { log.Errorf("failed to unmarshal latest yaml content, err: %s", err) return nil, err } - - // re-marshal it into string to make sure indentation is right - latestYamlContent, err := yaml.Marshal(tmp) + mergedValuesYamlBytes, err := yaml.Marshal(tmp) if err != nil { log.Errorf("failed to marshal latest yaml content, err: %s", err) return nil, err } - return &GetHelmValuesDifferenceResp{ - Current: currentYaml, - Latest: string(latestYamlContent), + return &HelmDeployJobMergeImageResponse{ + Values: string(mergedValuesYamlBytes), }, nil } +func genNewEnvService(prod *commonmodels.Product, serviceName string, updateServiceRevision bool) (*commonmodels.ProductService, error) { + prodSvc := prod.GetServiceMap()[serviceName] + if !updateServiceRevision { + if prodSvc == nil { + return nil, fmt.Errorf("service %s not found in env %s/%s", serviceName, prod.ProductName, prod.EnvName) + } + + svcFindOption := &commonrepo.ServiceFindOption{ + ProductName: prod.ProductName, + ServiceName: serviceName, + Revision: prodSvc.Revision, + } + curSvc, err := repository.QueryTemplateService(svcFindOption, prod.Production) + if err != nil { + return nil, errors.Wrapf(err, "failed to find service %s/%d in product %s", serviceName, svcFindOption.Revision, prod.ProductName) + } + + prodSvc.ValuesYaml = curSvc.HelmChart.ValuesYaml + } else { + svcFindOption := &commonrepo.ServiceFindOption{ + ProductName: prod.ProductName, + ServiceName: serviceName, + } + latestSvc, err := repository.QueryTemplateService(svcFindOption, prod.Production) + if err != nil { + return nil, errors.Wrapf(err, "failed to find service %s/%d in product %s", serviceName, svcFindOption.Revision, prod.ProductName) + } + + if prodSvc == nil { + prodSvc = &commonmodels.ProductService{ + ServiceName: serviceName, + Type: latestSvc.Type, + Revision: latestSvc.Revision, + Containers: latestSvc.Containers, + ValuesYaml: latestSvc.HelmChart.ValuesYaml, + } + } else { + containerMap := make(map[string]*commonmodels.Container) + for _, container := range prodSvc.Containers { + containerMap[container.Name] = container + } + + for _, templateContainer := range latestSvc.Containers { + if containerMap[templateContainer.Name] == nil { + prodSvc.Containers = append(prodSvc.Containers, templateContainer) + } else { + containerMap[templateContainer.Name].ImagePath = templateContainer.ImagePath + containerMap[templateContainer.Name].Type = templateContainer.Type + } + } + } + } + return prodSvc, nil +} + func GetMseOriginalServiceYaml(project, envName, serviceName, grayTag string) (string, error) { yamlContent, _, err := kube.FetchCurrentAppliedYaml(&kube.GeneSvcYamlOption{ ProductName: project,