From 18dd510bd33f708bc6385b1a0513ee5e0f38079b Mon Sep 17 00:00:00 2001 From: Timofey Kirillov Date: Wed, 18 May 2022 00:38:47 +0300 Subject: [PATCH] fix(post-renderer): non-strict labels and annotations validation in werf's post-renderer Signed-off-by: Timofey Kirillov --- cmd/werf/bundle/apply/apply.go | 6 +- cmd/werf/bundle/export/export.go | 5 +- cmd/werf/bundle/publish/publish.go | 5 +- cmd/werf/bundle/render/render.go | 7 +- cmd/werf/converge/converge.go | 7 +- cmd/werf/dismiss/dismiss.go | 4 +- cmd/werf/helm/helm.go | 4 +- cmd/werf/render/render.go | 11 +- pkg/deploy/helm/chart_extender/bundle.go | 9 +- .../chart_dependencies_loader.go | 2 +- pkg/deploy/helm/chart_extender/werf_chart.go | 19 +- .../helm/chart_extender/werf_chart_stub.go | 4 +- .../build_chart_dependencies.go | 9 +- ...ra_annotations_and_labels_post_renderer.go | 236 ++++++--- ...notations_and_labels_post_renderer_test.go | 500 ++++++++++++++++++ pkg/deploy/helm/suite_test.go | 13 + pkg/werf/global_warnings/global_warnings.go | 8 +- 17 files changed, 748 insertions(+), 101 deletions(-) create mode 100644 pkg/deploy/helm/extra_annotations_and_labels_post_renderer_test.go create mode 100644 pkg/deploy/helm/suite_test.go diff --git a/cmd/werf/bundle/apply/apply.go b/cmd/werf/bundle/apply/apply.go index 29ad49c447..63393b1953 100644 --- a/cmd/werf/bundle/apply/apply.go +++ b/cmd/werf/bundle/apply/apply.go @@ -185,8 +185,10 @@ func runApply() error { } bundle, err := chart_extender.NewBundle(ctx, bundleTmpDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.BundleOptions{ - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + BuildChartDependenciesOpts: command_helpers.BuildChartDependenciesOptions{IgnoreInvalidAnnotationsAndLabels: true}, + IgnoreInvalidAnnotationsAndLabels: true, + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, }) if err != nil { return err diff --git a/cmd/werf/bundle/export/export.go b/cmd/werf/bundle/export/export.go index 4d352ae7f1..e6103a5945 100644 --- a/cmd/werf/bundle/export/export.go +++ b/cmd/werf/bundle/export/export.go @@ -296,8 +296,9 @@ func runExport(ctx context.Context) error { } wc := chart_extender.NewWerfChart(ctx, giterminismManager, nil, chartDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.WerfChartOptions{ - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, + IgnoreInvalidAnnotationsAndLabels: true, }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { diff --git a/cmd/werf/bundle/publish/publish.go b/cmd/werf/bundle/publish/publish.go index efb4ccb978..8dc2cc30b1 100644 --- a/cmd/werf/bundle/publish/publish.go +++ b/cmd/werf/bundle/publish/publish.go @@ -327,8 +327,9 @@ func runPublish(ctx context.Context) error { } wc := chart_extender.NewWerfChart(ctx, giterminismManager, nil, chartDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.WerfChartOptions{ - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, + IgnoreInvalidAnnotationsAndLabels: true, }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { diff --git a/cmd/werf/bundle/render/render.go b/cmd/werf/bundle/render/render.go index 34d1f4ec88..ef254c3b3a 100644 --- a/cmd/werf/bundle/render/render.go +++ b/cmd/werf/bundle/render/render.go @@ -20,6 +20,7 @@ import ( "github.com/werf/werf/pkg/deploy/helm" "github.com/werf/werf/pkg/deploy/helm/chart_extender" "github.com/werf/werf/pkg/deploy/helm/chart_extender/helpers" + "github.com/werf/werf/pkg/deploy/helm/command_helpers" "github.com/werf/werf/pkg/storage" "github.com/werf/werf/pkg/werf" "github.com/werf/werf/pkg/werf/global_warnings" @@ -186,8 +187,10 @@ func runRender(ctx context.Context) error { } bundle, err := chart_extender.NewBundle(ctx, bundleDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.BundleOptions{ - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + BuildChartDependenciesOpts: command_helpers.BuildChartDependenciesOptions{IgnoreInvalidAnnotationsAndLabels: false}, + IgnoreInvalidAnnotationsAndLabels: false, + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, }) if err != nil { return err diff --git a/cmd/werf/converge/converge.go b/cmd/werf/converge/converge.go index d643808d8c..b02a32287a 100644 --- a/cmd/werf/converge/converge.go +++ b/cmd/werf/converge/converge.go @@ -376,9 +376,10 @@ func run(ctx context.Context, containerBackend container_backend.ContainerBacken } wc := chart_extender.NewWerfChart(ctx, giterminismManager, secretsManager, chartDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.WerfChartOptions{ - SecretValueFiles: common.GetSecretValues(&commonCmdData), - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + SecretValueFiles: common.GetSecretValues(&commonCmdData), + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, + IgnoreInvalidAnnotationsAndLabels: true, }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { diff --git a/cmd/werf/dismiss/dismiss.go b/cmd/werf/dismiss/dismiss.go index 2d44872610..d3d760f219 100644 --- a/cmd/werf/dismiss/dismiss.go +++ b/cmd/werf/dismiss/dismiss.go @@ -203,7 +203,9 @@ func runDismiss(ctx context.Context) error { return fmt.Errorf("unable to create helm registry client: %w", err) } - wc := chart_extender.NewWerfChart(ctx, giterminismManager, nil, chartDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.WerfChartOptions{}) + wc := chart_extender.NewWerfChart(ctx, giterminismManager, nil, chartDir, helm_v3.Settings, helmRegistryClientHandle, chart_extender.WerfChartOptions{ + IgnoreInvalidAnnotationsAndLabels: true, + }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { return err diff --git a/cmd/werf/helm/helm.go b/cmd/werf/helm/helm.go index a708611e66..551d7b92d6 100644 --- a/cmd/werf/helm/helm.go +++ b/cmd/werf/helm/helm.go @@ -43,11 +43,11 @@ func NewCmd() *cobra.Command { ctx := common.GetContext() - wc := chart_extender.NewWerfChartStub(ctx) + wc := chart_extender.NewWerfChartStub(ctx, false) loader.GlobalLoadOptions = &loader.LoadOptions{ ChartExtender: wc, - SubchartExtenderFactoryFunc: func() chart.ChartExtender { return chart_extender.NewWerfChartStub(ctx) }, + SubchartExtenderFactoryFunc: func() chart.ChartExtender { return chart_extender.NewWerfChartStub(ctx, false) }, } os.Setenv("HELM_EXPERIMENTAL_OCI", "1") diff --git a/cmd/werf/render/render.go b/cmd/werf/render/render.go index 495b948b37..128de04591 100644 --- a/cmd/werf/render/render.go +++ b/cmd/werf/render/render.go @@ -58,6 +58,10 @@ func NewCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { ctx := common.GetContext() + global_warnings.SuppressGlobalWarnings = true + if *commonCmdData.LogVerbose || *commonCmdData.LogDebug { + global_warnings.SuppressGlobalWarnings = false + } defer global_warnings.PrintGlobalWarnings(ctx) if err := common.ProcessLogOptions(&commonCmdData); err != nil { @@ -331,9 +335,10 @@ func runRender(ctx context.Context) error { } wc := chart_extender.NewWerfChart(ctx, giterminismManager, secretsManager, chartDir, helm_v3.Settings, helmRegistryClientHandler, chart_extender.WerfChartOptions{ - SecretValueFiles: common.GetSecretValues(&commonCmdData), - ExtraAnnotations: userExtraAnnotations, - ExtraLabels: userExtraLabels, + SecretValueFiles: common.GetSecretValues(&commonCmdData), + ExtraAnnotations: userExtraAnnotations, + ExtraLabels: userExtraLabels, + IgnoreInvalidAnnotationsAndLabels: false, }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { diff --git a/pkg/deploy/helm/chart_extender/bundle.go b/pkg/deploy/helm/chart_extender/bundle.go index 4c3b67e483..78db0cc492 100644 --- a/pkg/deploy/helm/chart_extender/bundle.go +++ b/pkg/deploy/helm/chart_extender/bundle.go @@ -24,9 +24,10 @@ import ( ) type BundleOptions struct { - BuildChartDependenciesOpts command_helpers.BuildChartDependenciesOptions - ExtraAnnotations map[string]string - ExtraLabels map[string]string + BuildChartDependenciesOpts command_helpers.BuildChartDependenciesOptions + ExtraAnnotations map[string]string + ExtraLabels map[string]string + IgnoreInvalidAnnotationsAndLabels bool } func NewBundle(ctx context.Context, dir string, helmEnvSettings *cli.EnvSettings, registryClient *registry.Client, opts BundleOptions) (*Bundle, error) { @@ -39,7 +40,7 @@ func NewBundle(ctx context.Context, dir string, helmEnvSettings *cli.EnvSettings ChartExtenderContextData: helpers.NewChartExtenderContextData(ctx), } - extraAnnotationsAndLabelsPostRenderer := helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil) + extraAnnotationsAndLabelsPostRenderer := helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil, opts.IgnoreInvalidAnnotationsAndLabels) if dataMap, err := readBundleJsonMap(filepath.Join(bundle.Dir, "extra_annotations.json")); err != nil { return nil, err diff --git a/pkg/deploy/helm/chart_extender/chart_dependencies_loader.go b/pkg/deploy/helm/chart_extender/chart_dependencies_loader.go index d08636e31d..83b42f2537 100644 --- a/pkg/deploy/helm/chart_extender/chart_dependencies_loader.go +++ b/pkg/deploy/helm/chart_extender/chart_dependencies_loader.go @@ -77,7 +77,7 @@ func GetPreparedChartDependenciesDir(ctx context.Context, metadataFile, metadata tmpDepsDir := fmt.Sprintf("%s.tmp.%s", depsDir, uuid.NewV4().String()) buildChartDependenciesOpts.LoadOptions = &loader.LoadOptions{ - ChartExtender: NewWerfChartStub(ctx), + ChartExtender: NewWerfChartStub(ctx, buildChartDependenciesOpts.IgnoreInvalidAnnotationsAndLabels), SubchartExtenderFactoryFunc: nil, } diff --git a/pkg/deploy/helm/chart_extender/werf_chart.go b/pkg/deploy/helm/chart_extender/werf_chart.go index 4fbcfe5477..590c97eaa0 100644 --- a/pkg/deploy/helm/chart_extender/werf_chart.go +++ b/pkg/deploy/helm/chart_extender/werf_chart.go @@ -31,11 +31,12 @@ import ( ) type WerfChartOptions struct { - SecretValueFiles []string - ExtraAnnotations map[string]string - ExtraLabels map[string]string - BuildChartDependenciesOpts command_helpers.BuildChartDependenciesOptions - DisableSecrets bool + SecretValueFiles []string + ExtraAnnotations map[string]string + ExtraLabels map[string]string + BuildChartDependenciesOpts command_helpers.BuildChartDependenciesOptions + DisableSecrets bool + IgnoreInvalidAnnotationsAndLabels bool } func NewWerfChart(ctx context.Context, giterminismManager giterminism_manager.Interface, secretsManager *secrets_manager.SecretsManager, chartDir string, helmEnvSettings *cli.EnvSettings, registryClient *registry.Client, opts WerfChartOptions) *WerfChart { @@ -49,7 +50,7 @@ func NewWerfChart(ctx context.Context, giterminismManager giterminism_manager.In GiterminismManager: giterminismManager, SecretsManager: secretsManager, - extraAnnotationsAndLabelsPostRenderer: helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil), + extraAnnotationsAndLabelsPostRenderer: helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil, opts.IgnoreInvalidAnnotationsAndLabels), ChartExtenderServiceValuesData: helpers.NewChartExtenderServiceValuesData(), ChartExtenderContextData: helpers.NewChartExtenderContextData(ctx), @@ -394,5 +395,9 @@ func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion } } - return NewBundle(ctx, destDir, wc.HelmEnvSettings, wc.RegistryClient, BundleOptions{BuildChartDependenciesOpts: wc.BuildChartDependenciesOpts}) + return NewBundle(ctx, destDir, wc.HelmEnvSettings, wc.RegistryClient, BundleOptions{ + BuildChartDependenciesOpts: wc.BuildChartDependenciesOpts, + IgnoreInvalidAnnotationsAndLabels: wc.extraAnnotationsAndLabelsPostRenderer.IgnoreInvalidAnnotationsAndLabels, + }, + ) } diff --git a/pkg/deploy/helm/chart_extender/werf_chart_stub.go b/pkg/deploy/helm/chart_extender/werf_chart_stub.go index 99d62d57ad..2eda7add53 100644 --- a/pkg/deploy/helm/chart_extender/werf_chart_stub.go +++ b/pkg/deploy/helm/chart_extender/werf_chart_stub.go @@ -17,9 +17,9 @@ import ( "github.com/werf/werf/pkg/deploy/secrets_manager" ) -func NewWerfChartStub(ctx context.Context) *WerfChartStub { +func NewWerfChartStub(ctx context.Context, ignoreInvalidAnnotationsAndLabels bool) *WerfChartStub { return &WerfChartStub{ - extraAnnotationsAndLabelsPostRenderer: helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil), + extraAnnotationsAndLabelsPostRenderer: helm.NewExtraAnnotationsAndLabelsPostRenderer(nil, nil, ignoreInvalidAnnotationsAndLabels), ChartExtenderContextData: helpers.NewChartExtenderContextData(ctx), } } diff --git a/pkg/deploy/helm/command_helpers/build_chart_dependencies.go b/pkg/deploy/helm/command_helpers/build_chart_dependencies.go index bb79ece98b..80ece71b7b 100644 --- a/pkg/deploy/helm/command_helpers/build_chart_dependencies.go +++ b/pkg/deploy/helm/command_helpers/build_chart_dependencies.go @@ -18,10 +18,11 @@ import ( ) type BuildChartDependenciesOptions struct { - Keyring string - SkipUpdate bool - Verify downloader.VerificationStrategy - LoadOptions *loader.LoadOptions + Keyring string + SkipUpdate bool + Verify downloader.VerificationStrategy + LoadOptions *loader.LoadOptions + IgnoreInvalidAnnotationsAndLabels bool } func BuildChartDependenciesInDir(ctx context.Context, chartFile, chartLockFile *chart.ChartExtenderBufferedFile, targetDir string, helmEnvSettings *cli.EnvSettings, registryClient *registry.Client, opts BuildChartDependenciesOptions) error { diff --git a/pkg/deploy/helm/extra_annotations_and_labels_post_renderer.go b/pkg/deploy/helm/extra_annotations_and_labels_post_renderer.go index 8cdb8144a0..88cb568c1c 100644 --- a/pkg/deploy/helm/extra_annotations_and_labels_post_renderer.go +++ b/pkg/deploy/helm/extra_annotations_and_labels_post_renderer.go @@ -2,8 +2,10 @@ package helm import ( "bytes" + "context" "fmt" "os" + "reflect" "sort" "strings" @@ -14,6 +16,7 @@ import ( "github.com/werf/logboek" "github.com/werf/werf/pkg/werf" + "github.com/werf/werf/pkg/werf/global_warnings" ) var WerfRuntimeAnnotations = map[string]string{ @@ -22,16 +25,31 @@ var WerfRuntimeAnnotations = map[string]string{ var WerfRuntimeLabels = map[string]string{} -func NewExtraAnnotationsAndLabelsPostRenderer(extraAnnotations, extraLabels map[string]string) *ExtraAnnotationsAndLabelsPostRenderer { +func NewExtraAnnotationsAndLabelsPostRenderer(extraAnnotations, extraLabels map[string]string, ignoreInvalidAnnotationsAndLabels bool) *ExtraAnnotationsAndLabelsPostRenderer { return &ExtraAnnotationsAndLabelsPostRenderer{ - ExtraAnnotations: extraAnnotations, - ExtraLabels: extraLabels, + ExtraAnnotations: extraAnnotations, + ExtraLabels: extraLabels, + IgnoreInvalidAnnotationsAndLabels: ignoreInvalidAnnotationsAndLabels, + globalWarnings: &defaultGlobalWarnings{}, } } type ExtraAnnotationsAndLabelsPostRenderer struct { - ExtraAnnotations map[string]string - ExtraLabels map[string]string + ExtraAnnotations map[string]string + ExtraLabels map[string]string + IgnoreInvalidAnnotationsAndLabels bool + + globalWarnings globalWarnings +} + +type defaultGlobalWarnings struct{} + +func (gw *defaultGlobalWarnings) GlobalWarningLn(ctx context.Context, msg string) { + global_warnings.GlobalWarningLn(ctx, msg) +} + +type globalWarnings interface { + GlobalWarningLn(ctx context.Context, msg string) } func replaceNodeByKey(node *yaml_v3.Node, key string, value *yaml_v3.Node) { @@ -82,6 +100,17 @@ func findNodeByKey(node *yaml_v3.Node, key string) *yaml_v3.Node { return nil } +func dereferenceAliasNode(node *yaml_v3.Node) (*yaml_v3.Node, error) { + dereferencedNode, err := createNode(map[string]interface{}{}) + if err != nil { + return nil, err + } + + dereferencedNode.Content = append(dereferencedNode.Content, node.Alias.Content...) + + return dereferencedNode, nil +} + func getMapNode(docNode *yaml_v3.Node) *yaml_v3.Node { if docNode.Kind == yaml_v3.DocumentNode { if len(docNode.Content) > 0 { @@ -94,13 +123,110 @@ func getMapNode(docNode *yaml_v3.Node) *yaml_v3.Node { return nil } -func createNode(v interface{}) *yaml_v3.Node { +func createNode(v interface{}) (*yaml_v3.Node, error) { newNode := &yaml_v3.Node{} if err := newNode.Encode(v); err != nil { - logboek.Warn().LogF("Unable to encode map %#v into yaml node: %s\n", v, err) - return nil + return nil, fmt.Errorf("unable to encode value %#v into yaml node: %w", v, err) } - return newNode + return newNode, nil +} + +func appendToNode(node *yaml_v3.Node, data interface{}) (*yaml_v3.Node, error) { + newNode, err := createNode(data) + if err != nil { + return nil, err + } + + node.Content = append(node.Content, newNode.Content...) + return node, nil +} + +func validateStringNode(node *yaml_v3.Node) error { + var v interface{} + if err := node.Decode(&v); err != nil { + return fmt.Errorf("unable to decode value %q: %w", node.Value, err) + } + if _, ok := v.(string); !ok { + return fmt.Errorf("invalid node %q: expected string, got %s", node.Value, reflect.TypeOf(v).String()) + } + return nil +} + +func validateKeyAndValue(keyNode, valueNode *yaml_v3.Node) (errors []error) { + keyErr := validateStringNode(keyNode) + if keyErr != nil { + errors = append(errors, keyErr) + } + + var valueErr error + if valueNode.Kind == yaml_v3.AliasNode { + valueErr = validateStringNode(valueNode.Alias) + } else { + valueErr = validateStringNode(valueNode) + } + if valueErr != nil { + errors = append(errors, valueErr) + } + + return +} + +func validateMapStringStringNode(node *yaml_v3.Node) ([]*yaml_v3.Node, []error) { + content := node.Content + end := len(content) + + var validValues []*yaml_v3.Node + var errors []error + + for pos := 0; pos < end; pos += 2 { + keyNode := content[pos] + valueNode := content[pos+1] + + if keyNode.Tag == "!!merge" && valueNode.Kind == yaml_v3.AliasNode { + for pos := 0; pos < len(valueNode.Alias.Content); pos += 2 { + keyNode := valueNode.Alias.Content[pos] + valueNode := valueNode.Alias.Content[pos+1] + + newErrs := validateKeyAndValue(keyNode, valueNode) + if len(newErrs) > 0 { + errors = append(errors, newErrs...) + } else { + validValues = append(validValues, keyNode, valueNode) + } + } + + continue + } + + newErrs := validateKeyAndValue(keyNode, valueNode) + if len(newErrs) > 0 { + errors = append(errors, newErrs...) + } else { + validValues = append(validValues, keyNode, valueNode) + } + } + + return validValues, errors +} + +func appendExtraData(node *yaml_v3.Node, key string, data interface{}) error { + if targetNode := findNodeByKey(node, key); targetNode != nil { + if targetNode.Kind == yaml_v3.AliasNode { + dereferencedTargetNode, err := dereferenceAliasNode(targetNode) + if err != nil { + return err + } + + appendToNode(dereferencedTargetNode, data) + replaceNodeByKey(node, key, dereferencedTargetNode) + } else { + appendToNode(targetNode, data) + } + } else { + appendToNode(node, map[string]interface{}{key: data}) + } + + return nil } func (pr *ExtraAnnotationsAndLabelsPostRenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { @@ -161,65 +287,45 @@ func (pr *ExtraAnnotationsAndLabelsPostRenderer) Run(renderedManifests *bytes.Bu fmt.Printf("Unpacket obj annotations: %#v\n", obj.GetAnnotations()) } - if obj.IsList() && len(extraAnnotations) > 0 { - logboek.Warn().LogF("werf annotations won't be applied to *List resource Kinds, including %s. We advise to replace *List resources with multiple separate resources of the same Kind\n", obj.GetKind()) - } else if len(extraAnnotations) > 0 { - if objMapNode := getMapNode(&objNode); objMapNode != nil { - if metadataNode := findNodeByKey(objMapNode, "metadata"); metadataNode != nil { - if annotationsNode := findNodeByKey(metadataNode, "annotations"); annotationsNode != nil { - if annotationsNode.Kind == yaml_v3.AliasNode { - if newMetadataAnnotationsNode := createNode(map[string]interface{}{"annotations": map[string]string{}}); newMetadataAnnotationsNode != nil { - if newAnnotationsNode := findNodeByKey(newMetadataAnnotationsNode, "annotations"); newAnnotationsNode != nil { - newAnnotationsNode.Content = append(newAnnotationsNode.Content, annotationsNode.Alias.Content...) - - if newExtraAnnotationsNode := createNode(extraAnnotations); newExtraAnnotationsNode != nil { - newAnnotationsNode.Content = append(newAnnotationsNode.Content, newExtraAnnotationsNode.Content...) - } - } - replaceNodeByKey(metadataNode, "annotations", newMetadataAnnotationsNode) - } - } else { - if newExtraAnnotationsNode := createNode(extraAnnotations); newExtraAnnotationsNode != nil { - annotationsNode.Content = append(annotationsNode.Content, newExtraAnnotationsNode.Content...) - } - } - } else { - if newMetadataAnnotationsNode := createNode(map[string]interface{}{"annotations": extraAnnotations}); newMetadataAnnotationsNode != nil { - metadataNode.Content = append(metadataNode.Content, newMetadataAnnotationsNode.Content...) - } + if objMapNode := getMapNode(&objNode); objMapNode != nil { + if metadataNode := findNodeByKey(objMapNode, "metadata"); metadataNode != nil { + + if obj.IsList() && len(extraAnnotations) > 0 { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("werf annotations won't be applied to *List resource Kinds, including %s. We advise to replace *List resources with multiple separate resources of the same Kind", obj.GetKind())) + } else if len(extraAnnotations) > 0 { + if err := appendExtraData(metadataNode, "annotations", extraAnnotations); err != nil { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("werf annotations won't be applied to the %s/%s: an error have occured during annotations injection: %s\n", strings.ToLower(obj.GetKind()), obj.GetName(), err)) } } - } - } - if obj.IsList() && len(extraLabels) > 0 { - logboek.Warn().LogF("werf labels won't be applied to *List resource Kinds, including %s. We advise to replace *List resources with multiple separate resources of the same Kind\n", obj.GetKind()) - } else if len(extraLabels) > 0 { - if objMapNode := getMapNode(&objNode); objMapNode != nil { - if metadataNode := findNodeByKey(objMapNode, "metadata"); metadataNode != nil { - if labelsNode := findNodeByKey(metadataNode, "labels"); labelsNode != nil { - if labelsNode.Kind == yaml_v3.AliasNode { - if newMetadataLabelsNode := createNode(map[string]interface{}{"labels": map[string]string{}}); newMetadataLabelsNode != nil { - if newLabelsNode := findNodeByKey(newMetadataLabelsNode, "labels"); newLabelsNode != nil { - newLabelsNode.Content = append(newLabelsNode.Content, labelsNode.Alias.Content...) - - if newExtraLabelsNode := createNode(extraLabels); newExtraLabelsNode != nil { - newLabelsNode.Content = append(newLabelsNode.Content, newExtraLabelsNode.Content...) - } - } - replaceNodeByKey(metadataNode, "labels", newMetadataLabelsNode) - } - } else { - if newExtraLabelsNode := createNode(extraLabels); newExtraLabelsNode != nil { - labelsNode.Content = append(labelsNode.Content, newExtraLabelsNode.Content...) - } - } - } else { - if newMetadataLabelsNode := createNode(map[string]interface{}{"labels": extraLabels}); newMetadataLabelsNode != nil { - metadataNode.Content = append(metadataNode.Content, newMetadataLabelsNode.Content...) - } + if obj.IsList() && len(extraLabels) > 0 { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("werf labels won't be applied to *List resource Kinds, including %s. We advise to replace *List resources with multiple separate resources of the same Kind", obj.GetKind())) + } else if len(extraLabels) > 0 { + if err := appendExtraData(metadataNode, "labels", extraLabels); err != nil { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("werf labels won't be applied to the %s/%s: an error have occured during labels injection: %s\n", strings.ToLower(obj.GetKind()), obj.GetName(), err)) } } + + if annotationsNode := findNodeByKey(metadataNode, "annotations"); annotationsNode != nil { + validNodes, errors := validateMapStringStringNode(annotationsNode) + for _, err := range errors { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("%s/%s annotations validation: %s", strings.ToLower(obj.GetKind()), obj.GetName(), err.Error())) + } + if pr.IgnoreInvalidAnnotationsAndLabels { + annotationsNode.Content = validNodes + } + } + + if labelsNode := findNodeByKey(metadataNode, "labels"); labelsNode != nil { + validNodes, errors := validateMapStringStringNode(labelsNode) + for _, err := range errors { + pr.globalWarnings.GlobalWarningLn(context.Background(), fmt.Sprintf("%s/%s labels validation: %s\n", strings.ToLower(obj.GetKind()), obj.GetName(), err.Error())) + } + if pr.IgnoreInvalidAnnotationsAndLabels { + labelsNode.Content = validNodes + } + } + } } @@ -234,13 +340,13 @@ func (pr *ExtraAnnotationsAndLabelsPostRenderer) Run(renderedManifests *bytes.Bu if os.Getenv("WERF_HELM_V3_EXTRA_ANNOTATIONS_AND_LABELS_DEBUG") == "1" { fmt.Printf("ExtraAnnotationsAndLabelsPostRenderer -- modified manifest BEGIN\n") - fmt.Printf("%s\n", modifiedManifestContent.String()) + fmt.Printf("%s", modifiedManifestContent.String()) fmt.Printf("ExtraAnnotationsAndLabelsPostRenderer -- modified manifest END\n") } } } - modifiedManifests := bytes.NewBufferString(strings.Join(splitModifiedManifests, "\n---\n")) + modifiedManifests := bytes.NewBufferString(strings.Join(splitModifiedManifests, "---\n")) if os.Getenv("WERF_HELM_V3_EXTRA_ANNOTATIONS_AND_LABELS_DEBUG") == "1" { fmt.Printf("ExtraAnnotationsAndLabelsPostRenderer -- modified manifests RESULT BEGIN\n") fmt.Printf("%s\n", modifiedManifests.String()) diff --git a/pkg/deploy/helm/extra_annotations_and_labels_post_renderer_test.go b/pkg/deploy/helm/extra_annotations_and_labels_post_renderer_test.go new file mode 100644 index 0000000000..56028b87b4 --- /dev/null +++ b/pkg/deploy/helm/extra_annotations_and_labels_post_renderer_test.go @@ -0,0 +1,500 @@ +package helm + +import ( + "bytes" + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/werf/werf/pkg/werf" +) + +type ExtraAnnotationsAndLabelsPostRendererTestData struct { + PostRenderer *ExtraAnnotationsAndLabelsPostRenderer + Manifest string + ExpectedManifest string + ExpectedWarningsCount int +} + +type globalWarningsMock struct { + messages []string +} + +func (gw *globalWarningsMock) GlobalWarningLn(ctx context.Context, msg string) { + gw.messages = append(gw.messages, msg) +} + +var _ = Describe("ExtraAnnotationsAndLabelsPostRenderer", func() { + DescribeTable("parsing manifests stream and injecting custom annotations and labels", + func(data ExtraAnnotationsAndLabelsPostRendererTestData) { + globalWarnings := &globalWarningsMock{} + data.PostRenderer.globalWarnings = globalWarnings + + out, err := data.PostRenderer.Run(bytes.NewBuffer([]byte(data.Manifest))) + Expect(err).ShouldNot(HaveOccurred()) + + fmt.Printf("OUT:\n%s\n---\n", out.String()) + fmt.Printf("EXPECTED OUT:\n%s\n---\n", data.ExpectedManifest) + Expect(out.String()).To(Equal(data.ExpectedManifest)) + for _, msg := range globalWarnings.messages { + fmt.Printf("WARNING: %s\n", msg) + } + Expect(len(globalWarnings.messages)).To(Equal(data.ExpectedWarningsCount)) + }, + + Entry("should add builtin extra annotations into resources manifests", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer(nil, nil, false), + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + annotations: + one: two + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: result + name: result +spec: + ports: + - name: "result-service" + port: 80 + selector: + app: result +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + annotations: + one: two + werf.io/version: %s + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: result + name: result + annotations: + werf.io/version: %s +spec: + ports: + - name: "result-service" + port: 80 + selector: + app: result +`, werf.Version, werf.Version), + }), + + Entry("should add builtin and extra annotations and labels into resources manifests", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer( + map[string]string{"test-annotation-1": "value-1", "test-annotation-2": "value-2"}, + map[string]string{"test-label-1": "value-1", "test-label-2": "value-2"}, + false, + ), + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + annotations: + one: two + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: result + name: result +spec: + ports: + - name: "result-service" + port: 80 + selector: + app: result +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + test-label-1: value-1 + test-label-2: value-2 + annotations: + one: two + test-annotation-1: value-1 + test-annotation-2: value-2 + werf.io/version: %s + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: result + test-label-1: value-1 + test-label-2: value-2 + name: result + annotations: + test-annotation-1: value-1 + test-annotation-2: value-2 + werf.io/version: %s +spec: + ports: + - name: "result-service" + port: 80 + selector: + app: result +`, werf.Version, werf.Version), + }), + + Entry("should add extra annotations into yaml alias node defined by yaml anchor", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer( + map[string]string{"test-annotation-1": "value-1", "test-annotation-2": "value-2"}, + nil, + false, + ), + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: &named_anchor + app: vote + annotations: *named_anchor + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: &named_anchor + app: vote + annotations: {app: vote, test-annotation-1: value-1, test-annotation-2: value-2, werf.io/version: %s} + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, werf.Version), + }), + + Entry("should add extra annotations into yaml node which references some yaml anchor", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer( + map[string]string{"test-annotation-1": "value-1", "test-annotation-2": "value-2"}, + nil, + false, + ), + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: &named_anchor + app: vote + annotations: + <<: *named_anchor + test-key: test-value + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: &named_anchor + app: vote + annotations: + !!merge <<: *named_anchor + test-key: test-value + test-annotation-1: value-1 + test-annotation-2: value-2 + werf.io/version: %s + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, werf.Version), + }), + + Entry("should print warnings for invalid annotations and labels which are not strings, should render invalid values though", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer( + nil, + nil, + false, + ), + ExpectedWarningsCount: 2, + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + one: 1 + annotations: + hello: world + two: 2 + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + one: 1 + annotations: + hello: world + two: 2 + werf.io/version: %s + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, werf.Version), + }), + + Entry("should print warnings for invalid annotations and labels which are not strings, should omit invalid values", + ExtraAnnotationsAndLabelsPostRendererTestData{ + PostRenderer: NewExtraAnnotationsAndLabelsPostRenderer( + nil, + nil, + true, + ), + ExpectedWarningsCount: 2, + Manifest: `apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + one: 1 + annotations: + hello: world + two: 2 + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, + ExpectedManifest: fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + annotations: + hello: world + werf.io/version: %s + name: vote +spec: + replicas: 1 + selector: + matchLabels: + app: vote + template: + metadata: + labels: + app: vote + spec: + imagePullSecrets: + - name: registrysecret + containers: + - image: myimage + name: vote + ports: + - containerPort: 80 + name: vote +`, werf.Version), + }), + ) +}) diff --git a/pkg/deploy/helm/suite_test.go b/pkg/deploy/helm/suite_test.go new file mode 100644 index 0000000000..ed1563ec7c --- /dev/null +++ b/pkg/deploy/helm/suite_test.go @@ -0,0 +1,13 @@ +package helm + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestStage(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "deploy/helm suite") +} diff --git a/pkg/werf/global_warnings/global_warnings.go b/pkg/werf/global_warnings/global_warnings.go index 313d27e625..732f4bf68d 100644 --- a/pkg/werf/global_warnings/global_warnings.go +++ b/pkg/werf/global_warnings/global_warnings.go @@ -14,7 +14,10 @@ import ( const LastMultiwerfVersion = "1.5.0" -var GlobalWarningLines []string +var ( + GlobalWarningLines []string + SuppressGlobalWarnings bool +) func PrintGlobalWarnings(ctx context.Context) { for _, line := range GlobalWarningLines { @@ -78,5 +81,8 @@ func PostponeMultiwerfNotUpToDateWarning() { } func printGlobalWarningLn(ctx context.Context, line string) { + if SuppressGlobalWarnings { + return + } logboek.Context(ctx).Error().LogF("WARNING: %s\n", line) }