From 5abc0d412c4fa418b5c3a787971766bb67db2bef Mon Sep 17 00:00:00 2001 From: Tere Date: Tue, 16 Dec 2025 15:44:56 +0100 Subject: [PATCH 1/4] implement shared tags functionality to avoid duplicates in exports --- internal/export/dashboards.go | 32 +++++++++ internal/export/dashboards_test.go | 43 ++++++++++++ internal/export/object_transformer.go | 1 + .../transform_remove_fleet_managed_tags.go | 42 +++++++++++ .../export/transform_remove_shared_tags.go | 37 ++++++++++ .../transform_remove_shared_tags_test.go | 69 +++++++++++++++++++ 6 files changed, 224 insertions(+) create mode 100644 internal/export/transform_remove_shared_tags.go create mode 100644 internal/export/transform_remove_shared_tags_test.go diff --git a/internal/export/dashboards.go b/internal/export/dashboards.go index df57b85ac4..8208da65a9 100644 --- a/internal/export/dashboards.go +++ b/internal/export/dashboards.go @@ -7,11 +7,13 @@ package export import ( "context" "encoding/json" + "errors" "fmt" "os" "path/filepath" "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert/yaml" "github.com/elastic/elastic-package/internal/common" "github.com/elastic/elastic-package/internal/kibana" @@ -46,8 +48,14 @@ func Dashboards(ctx context.Context, kibanaClient *kibana.Client, dashboardsIDs return fmt.Errorf("exporting dashboards using Kibana client failed: %w", err) } + sharedTags, err := readSharedTagsFile(packageRoot) + if err != nil { + return fmt.Errorf("reading shared tags file failed: %w", err) + } + transformContext := &transformationContext{ packageName: m.Name, + sharedTags: sharedTags, } objects, err = applyTransformations(transformContext, objects) @@ -87,6 +95,7 @@ func applyTransformations(ctx *transformationContext, objects []common.MapStr) ( stripObjectProperties, standardizeObjectProperties, removeFleetManagedTags, + removeDuplicateSharedTags, standardizeObjectID). transform(objects) } @@ -120,3 +129,26 @@ func saveObjectsToFiles(packageRoot string, objects []common.MapStr) error { } return nil } + +func readSharedTagsFile(packageRoot string) ([]string, error) { + b, err := os.ReadFile(filepath.Join(packageRoot, "kibana", "tags.yml")) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // No shared tags file, return empty list + return nil, nil + } + return nil, fmt.Errorf("reading shared tags file failed: %w", err) + } + var sharedTags []struct { + Text string `yaml:"text"` + } + err = yaml.Unmarshal(b, &sharedTags) + if err != nil { + return nil, fmt.Errorf("unmarshalling shared tags file failed: %w", err) + } + tags := make([]string, 0, len(sharedTags)) + for _, tag := range sharedTags { + tags = append(tags, tag.Text) + } + return tags, nil +} diff --git a/internal/export/dashboards_test.go b/internal/export/dashboards_test.go index be884deb0c..283b5d3134 100644 --- a/internal/export/dashboards_test.go +++ b/internal/export/dashboards_test.go @@ -7,6 +7,7 @@ package export import ( "encoding/json" "os" + "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -38,3 +39,45 @@ func TestTransform(t *testing.T) { require.Equal(t, string(expected), string(result)) } + +func TestReadSharedTagsFile(t *testing.T) { + + t.Run("file exists", func(t *testing.T) { + tmpDir := t.TempDir() + + err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1 +- text: tag2 +- text: tag3 +`), 0644) + require.NoError(t, err) + + tags, err := readSharedTagsFile(tmpDir) + require.NoError(t, err) + require.Equal(t, []string{"tag1", "tag2", "tag3"}, tags) + }) + + t.Run("file does not exist", func(t *testing.T) { + tmpDir := t.TempDir() + + tags, err := readSharedTagsFile(tmpDir) + require.NoError(t, err) + require.Empty(t, tags) + }) + + t.Run("invalid YAML", func(t *testing.T) { + tmpDir := t.TempDir() + + err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1 +- text +- text: tag3 +`), 0644) + require.NoError(t, err) + + _, err = readSharedTagsFile(tmpDir) + require.Error(t, err) + }) +} diff --git a/internal/export/object_transformer.go b/internal/export/object_transformer.go index cb333d6fd2..8eb93a6a94 100644 --- a/internal/export/object_transformer.go +++ b/internal/export/object_transformer.go @@ -17,6 +17,7 @@ type objectTransformer struct { type transformationContext struct { packageName string + sharedTags []string } func newObjectTransformer() *objectTransformer { diff --git a/internal/export/transform_remove_fleet_managed_tags.go b/internal/export/transform_remove_fleet_managed_tags.go index 352f545a0f..e077a99509 100644 --- a/internal/export/transform_remove_fleet_managed_tags.go +++ b/internal/export/transform_remove_fleet_managed_tags.go @@ -6,6 +6,7 @@ package export import ( "fmt" + "strings" "github.com/elastic/elastic-package/internal/common" ) @@ -38,6 +39,11 @@ func removeTagReferences(ctx *transformationContext, object common.MapStr) (comm return nil, err } + newReferences, err = filterOutSharedTags(ctx, newReferences) + if err != nil { + return nil, err + } + _, err = object.Put("references", newReferences) if err != nil { return nil, fmt.Errorf("can't update references: %w", err) @@ -80,6 +86,42 @@ func isTagFleetManaged(aId, packageName string) bool { return ok } +func isSharedTag(aId string, sharedTags []string) bool { + for _, tag := range sharedTags { + id := fmt.Sprintf("tag-ref-%s-default", strings.ReplaceAll(strings.ToLower(tag), " ", "-")) + if aId == id { + return true + } + } + return false +} + +func filterOutSharedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { + newReferences := make([]interface{}, 0) + for _, r := range references { + reference := r.(map[string]interface{}) + + aType, ok := reference["type"] + if !ok { + continue + } + if aType != "tag" { + newReferences = append(newReferences, r) + continue + } + + aNameString, ok := reference["name"].(string) + if !ok { + return nil, fmt.Errorf("failed to assert name as a string: %v", reference["name"]) + } + if isSharedTag(aNameString, ctx.sharedTags) { + continue + } + newReferences = append(newReferences, r) + } + return newReferences, nil +} + func filterOutFleetManagedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { newReferences := make([]interface{}, 0) for _, r := range references { diff --git a/internal/export/transform_remove_shared_tags.go b/internal/export/transform_remove_shared_tags.go new file mode 100644 index 0000000000..923b11c3dc --- /dev/null +++ b/internal/export/transform_remove_shared_tags.go @@ -0,0 +1,37 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package export + +import ( + "fmt" + "slices" + + "github.com/elastic/elastic-package/internal/common" +) + +func removeDuplicateSharedTags(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { + aType, err := object.GetValue("type") + if err != nil { + return nil, fmt.Errorf("failed to read type field: %w", err) + } + + if aType != "tag" { + return object, nil + } + tagName, err := object.GetValue("attributes.name") + if err != nil { + return nil, fmt.Errorf("failed to read attributes.name field: %w", err) + } + + tagNameStr, ok := tagName.(string) + if !ok { + return nil, fmt.Errorf("failed to convert tag name to string") + } + if slices.Contains(ctx.sharedTags, tagNameStr) { + // Tag is already in the shared tags list, remove it + return nil, nil + } + return object, nil +} diff --git a/internal/export/transform_remove_shared_tags_test.go b/internal/export/transform_remove_shared_tags_test.go new file mode 100644 index 0000000000..79b8394503 --- /dev/null +++ b/internal/export/transform_remove_shared_tags_test.go @@ -0,0 +1,69 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package export + +import ( + "testing" + + "github.com/elastic/elastic-package/internal/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRemoveDuplicateSharedTags(t *testing.T) { + tests := []struct { + name string + sharedTags []string + inputObject common.MapStr + expectedObject common.MapStr + }{ + { + name: "Tag already in shared tags", + sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + inputObject: map[string]interface{}{ + "type": "tag", + "attributes": map[string]interface{}{"name": "shared-tag-1"}, + }, + expectedObject: nil, + }, + { + name: "Tag not in shared tags", + sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + inputObject: map[string]interface{}{ + "type": "tag", + "attributes": map[string]interface{}{"name": "unique-tag"}, + }, + expectedObject: map[string]interface{}{ + "type": "tag", + "attributes": map[string]interface{}{"name": "unique-tag"}, + }, + }, + { + name: "Non-tag object", + sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + inputObject: map[string]interface{}{ + "type": "dashboard", + "attributes": map[string]interface{}{"title": "My Dashboard"}, + }, + expectedObject: map[string]interface{}{ + "type": "dashboard", + "attributes": map[string]interface{}{"title": "My Dashboard"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := &transformationContext{ + sharedTags: tt.sharedTags, + } + inputMapStr := tt.inputObject + result, err := removeDuplicateSharedTags(ctx, inputMapStr) + require.NoError(t, err) + + assert.Equal(t, tt.expectedObject, result) + }) + } +} From 5e4b631a1261a2445f8a940b2c9d3a3db637a096 Mon Sep 17 00:00:00 2001 From: Tere Date: Tue, 16 Dec 2025 16:23:48 +0100 Subject: [PATCH 2/4] fix: reorder import statements for consistency --- internal/export/transform_remove_shared_tags_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/export/transform_remove_shared_tags_test.go b/internal/export/transform_remove_shared_tags_test.go index 79b8394503..ac4c6caf72 100644 --- a/internal/export/transform_remove_shared_tags_test.go +++ b/internal/export/transform_remove_shared_tags_test.go @@ -7,9 +7,10 @@ package export import ( "testing" - "github.com/elastic/elastic-package/internal/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-package/internal/common" ) func TestRemoveDuplicateSharedTags(t *testing.T) { From 3641e77989f5a558e16604a887cbe946feb6a78d Mon Sep 17 00:00:00 2001 From: Tere Date: Wed, 17 Dec 2025 12:00:43 +0100 Subject: [PATCH 3/4] refactor shared tag logic, add reference to fleet id creation --- internal/export/dashboards.go | 31 ------------- internal/export/dashboards_test.go | 43 ------------------- internal/export/object_transformer.go | 1 - .../transform_remove_fleet_managed_tags.go | 23 +++++----- .../export/transform_remove_shared_tags.go | 12 +++--- .../transform_remove_shared_tags_test.go | 42 +++++++++--------- 6 files changed, 39 insertions(+), 113 deletions(-) diff --git a/internal/export/dashboards.go b/internal/export/dashboards.go index 8208da65a9..4ffe6af8ba 100644 --- a/internal/export/dashboards.go +++ b/internal/export/dashboards.go @@ -7,13 +7,11 @@ package export import ( "context" "encoding/json" - "errors" "fmt" "os" "path/filepath" "github.com/Masterminds/semver/v3" - "github.com/stretchr/testify/assert/yaml" "github.com/elastic/elastic-package/internal/common" "github.com/elastic/elastic-package/internal/kibana" @@ -48,14 +46,8 @@ func Dashboards(ctx context.Context, kibanaClient *kibana.Client, dashboardsIDs return fmt.Errorf("exporting dashboards using Kibana client failed: %w", err) } - sharedTags, err := readSharedTagsFile(packageRoot) - if err != nil { - return fmt.Errorf("reading shared tags file failed: %w", err) - } - transformContext := &transformationContext{ packageName: m.Name, - sharedTags: sharedTags, } objects, err = applyTransformations(transformContext, objects) @@ -129,26 +121,3 @@ func saveObjectsToFiles(packageRoot string, objects []common.MapStr) error { } return nil } - -func readSharedTagsFile(packageRoot string) ([]string, error) { - b, err := os.ReadFile(filepath.Join(packageRoot, "kibana", "tags.yml")) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // No shared tags file, return empty list - return nil, nil - } - return nil, fmt.Errorf("reading shared tags file failed: %w", err) - } - var sharedTags []struct { - Text string `yaml:"text"` - } - err = yaml.Unmarshal(b, &sharedTags) - if err != nil { - return nil, fmt.Errorf("unmarshalling shared tags file failed: %w", err) - } - tags := make([]string, 0, len(sharedTags)) - for _, tag := range sharedTags { - tags = append(tags, tag.Text) - } - return tags, nil -} diff --git a/internal/export/dashboards_test.go b/internal/export/dashboards_test.go index 283b5d3134..be884deb0c 100644 --- a/internal/export/dashboards_test.go +++ b/internal/export/dashboards_test.go @@ -7,7 +7,6 @@ package export import ( "encoding/json" "os" - "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -39,45 +38,3 @@ func TestTransform(t *testing.T) { require.Equal(t, string(expected), string(result)) } - -func TestReadSharedTagsFile(t *testing.T) { - - t.Run("file exists", func(t *testing.T) { - tmpDir := t.TempDir() - - err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755) - require.NoError(t, err) - err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1 -- text: tag2 -- text: tag3 -`), 0644) - require.NoError(t, err) - - tags, err := readSharedTagsFile(tmpDir) - require.NoError(t, err) - require.Equal(t, []string{"tag1", "tag2", "tag3"}, tags) - }) - - t.Run("file does not exist", func(t *testing.T) { - tmpDir := t.TempDir() - - tags, err := readSharedTagsFile(tmpDir) - require.NoError(t, err) - require.Empty(t, tags) - }) - - t.Run("invalid YAML", func(t *testing.T) { - tmpDir := t.TempDir() - - err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755) - require.NoError(t, err) - err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1 -- text -- text: tag3 -`), 0644) - require.NoError(t, err) - - _, err = readSharedTagsFile(tmpDir) - require.Error(t, err) - }) -} diff --git a/internal/export/object_transformer.go b/internal/export/object_transformer.go index 8eb93a6a94..cb333d6fd2 100644 --- a/internal/export/object_transformer.go +++ b/internal/export/object_transformer.go @@ -17,7 +17,6 @@ type objectTransformer struct { type transformationContext struct { packageName string - sharedTags []string } func newObjectTransformer() *objectTransformer { diff --git a/internal/export/transform_remove_fleet_managed_tags.go b/internal/export/transform_remove_fleet_managed_tags.go index e077a99509..22d6d49500 100644 --- a/internal/export/transform_remove_fleet_managed_tags.go +++ b/internal/export/transform_remove_fleet_managed_tags.go @@ -86,14 +86,15 @@ func isTagFleetManaged(aId, packageName string) bool { return ok } -func isSharedTag(aId string, sharedTags []string) bool { - for _, tag := range sharedTags { - id := fmt.Sprintf("tag-ref-%s-default", strings.ReplaceAll(strings.ToLower(tag), " ", "-")) - if aId == id { - return true - } - } - return false +// isSharedTag checks if the given tag ID is a shared tag for the specified package. +// Shared tags ids are created by fleet form the tags.yml file in the package. +// https://github.com/elastic/kibana/blob/5385f96a132114362b2542e6a44c96a697b66c28/x-pack/platform/plugins/shared/fleet/server/services/epm/kibana/assets/tag_assets.ts#L67 +func isSharedTag(aId string, packageName string) bool { + defaultSharedTagTemplate := "fleet-shared-tag-%s" + securitySolutionTagTemplate := "%s-security-solution" + + return strings.Contains(aId, fmt.Sprintf(defaultSharedTagTemplate, packageName)) || + strings.Contains(aId, fmt.Sprintf(securitySolutionTagTemplate, packageName)) } func filterOutSharedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { @@ -110,11 +111,11 @@ func filterOutSharedTags(ctx *transformationContext, references []interface{}) ( continue } - aNameString, ok := reference["name"].(string) + aId, ok := reference["id"].(string) if !ok { - return nil, fmt.Errorf("failed to assert name as a string: %v", reference["name"]) + return nil, fmt.Errorf("failed to assert name as a string: %v", reference["id"]) } - if isSharedTag(aNameString, ctx.sharedTags) { + if isSharedTag(aId, ctx.packageName) { continue } newReferences = append(newReferences, r) diff --git a/internal/export/transform_remove_shared_tags.go b/internal/export/transform_remove_shared_tags.go index 923b11c3dc..acb9560160 100644 --- a/internal/export/transform_remove_shared_tags.go +++ b/internal/export/transform_remove_shared_tags.go @@ -6,7 +6,6 @@ package export import ( "fmt" - "slices" "github.com/elastic/elastic-package/internal/common" ) @@ -20,17 +19,16 @@ func removeDuplicateSharedTags(ctx *transformationContext, object common.MapStr) if aType != "tag" { return object, nil } - tagName, err := object.GetValue("attributes.name") + tagId, err := object.GetValue("id") if err != nil { - return nil, fmt.Errorf("failed to read attributes.name field: %w", err) + return nil, fmt.Errorf("failed to read id field: %w", err) } - tagNameStr, ok := tagName.(string) + tagIdStr, ok := tagId.(string) if !ok { - return nil, fmt.Errorf("failed to convert tag name to string") + return nil, fmt.Errorf("failed to convert tag id to string") } - if slices.Contains(ctx.sharedTags, tagNameStr) { - // Tag is already in the shared tags list, remove it + if isSharedTag(tagIdStr, ctx.packageName) { return nil, nil } return object, nil diff --git a/internal/export/transform_remove_shared_tags_test.go b/internal/export/transform_remove_shared_tags_test.go index ac4c6caf72..eb32712394 100644 --- a/internal/export/transform_remove_shared_tags_test.go +++ b/internal/export/transform_remove_shared_tags_test.go @@ -16,50 +16,52 @@ import ( func TestRemoveDuplicateSharedTags(t *testing.T) { tests := []struct { name string - sharedTags []string inputObject common.MapStr expectedObject common.MapStr }{ { - name: "Tag already in shared tags", - sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + name: "Is default shared tag", inputObject: map[string]interface{}{ - "type": "tag", - "attributes": map[string]interface{}{"name": "shared-tag-1"}, + "type": "tag", + "id": "fleet-shared-tag-system-default", }, expectedObject: nil, }, { - name: "Tag not in shared tags", - sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + name: "Is security solution shared tag", inputObject: map[string]interface{}{ - "type": "tag", - "attributes": map[string]interface{}{"name": "unique-tag"}, + "type": "tag", + "id": "system-security-solution-default", + }, + expectedObject: nil, + }, + { + name: "Is not shared tag", + inputObject: map[string]interface{}{ + "type": "tag", + "id": "unique-tag", }, expectedObject: map[string]interface{}{ - "type": "tag", - "attributes": map[string]interface{}{"name": "unique-tag"}, + "type": "tag", + "id": "unique-tag", }, }, { - name: "Non-tag object", - sharedTags: []string{"shared-tag-1", "shared-tag-2"}, + name: "Non-tag object", inputObject: map[string]interface{}{ - "type": "dashboard", - "attributes": map[string]interface{}{"title": "My Dashboard"}, + "type": "dashboard", + "id": "My Dashboard", }, expectedObject: map[string]interface{}{ - "type": "dashboard", - "attributes": map[string]interface{}{"title": "My Dashboard"}, + "type": "dashboard", + "id": "My Dashboard", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := &transformationContext{ - sharedTags: tt.sharedTags, - } + ctx := &transformationContext{} inputMapStr := tt.inputObject result, err := removeDuplicateSharedTags(ctx, inputMapStr) require.NoError(t, err) From a4ba48c3b6b6389aad92d31743375b8a269f5f10 Mon Sep 17 00:00:00 2001 From: Tere Date: Wed, 17 Dec 2025 12:23:56 +0100 Subject: [PATCH 4/4] consolidate tag removal logic and add tests cases --- internal/export/dashboards.go | 3 +- ...m_remove_fleet_managed_tags.go => tags.go} | 56 ++++++++------- ...leet_managed_tags_test.go => tags_test.go} | 14 +++- .../elastic_package_registry.dashboard.json | 12 +++- ...c_package_registry.shared_tag_default.json | 15 ++++ ...registry.shared_tag_security_solution.json | 15 ++++ .../export/transform_remove_shared_tags.go | 35 --------- .../transform_remove_shared_tags_test.go | 72 ------------------- 8 files changed, 84 insertions(+), 138 deletions(-) rename internal/export/{transform_remove_fleet_managed_tags.go => tags.go} (94%) rename internal/export/{transform_remove_fleet_managed_tags_test.go => tags_test.go} (82%) create mode 100644 internal/export/testdata/elastic_package_registry.shared_tag_default.json create mode 100644 internal/export/testdata/elastic_package_registry.shared_tag_security_solution.json delete mode 100644 internal/export/transform_remove_shared_tags.go delete mode 100644 internal/export/transform_remove_shared_tags_test.go diff --git a/internal/export/dashboards.go b/internal/export/dashboards.go index 4ffe6af8ba..bf178043c6 100644 --- a/internal/export/dashboards.go +++ b/internal/export/dashboards.go @@ -86,8 +86,7 @@ func applyTransformations(ctx *transformationContext, objects []common.MapStr) ( decodeObject, stripObjectProperties, standardizeObjectProperties, - removeFleetManagedTags, - removeDuplicateSharedTags, + removeFleetTags, standardizeObjectID). transform(objects) } diff --git a/internal/export/transform_remove_fleet_managed_tags.go b/internal/export/tags.go similarity index 94% rename from internal/export/transform_remove_fleet_managed_tags.go rename to internal/export/tags.go index 22d6d49500..f2c26a20b4 100644 --- a/internal/export/transform_remove_fleet_managed_tags.go +++ b/internal/export/tags.go @@ -11,7 +11,8 @@ import ( "github.com/elastic/elastic-package/internal/common" ) -func removeFleetManagedTags(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { +// removeFleetTags removes fleet managed and shared tags from the given Kibana object. +func removeFleetTags(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { aType, err := object.GetValue("type") if err != nil { return nil, fmt.Errorf("failed to read type field: %w", err) @@ -70,6 +71,9 @@ func removeTagObjects(ctx *transformationContext, object common.MapStr) (common. if isTagFleetManaged(aIdString, ctx.packageName) { return nil, nil } + if isSharedTag(aIdString, ctx.packageName) { + return nil, nil + } return object, nil } @@ -86,18 +90,7 @@ func isTagFleetManaged(aId, packageName string) bool { return ok } -// isSharedTag checks if the given tag ID is a shared tag for the specified package. -// Shared tags ids are created by fleet form the tags.yml file in the package. -// https://github.com/elastic/kibana/blob/5385f96a132114362b2542e6a44c96a697b66c28/x-pack/platform/plugins/shared/fleet/server/services/epm/kibana/assets/tag_assets.ts#L67 -func isSharedTag(aId string, packageName string) bool { - defaultSharedTagTemplate := "fleet-shared-tag-%s" - securitySolutionTagTemplate := "%s-security-solution" - - return strings.Contains(aId, fmt.Sprintf(defaultSharedTagTemplate, packageName)) || - strings.Contains(aId, fmt.Sprintf(securitySolutionTagTemplate, packageName)) -} - -func filterOutSharedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { +func filterOutFleetManagedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { newReferences := make([]interface{}, 0) for _, r := range references { reference := r.(map[string]interface{}) @@ -111,11 +104,17 @@ func filterOutSharedTags(ctx *transformationContext, references []interface{}) ( continue } - aId, ok := reference["id"].(string) + aId, ok := reference["id"] if !ok { - return nil, fmt.Errorf("failed to assert name as a string: %v", reference["id"]) + newReferences = append(newReferences, r) + continue } - if isSharedTag(aId, ctx.packageName) { + + aIdString, ok := aId.(string) + if !ok { + return nil, fmt.Errorf("failed to assert id as a string: %v", aId) + } + if isTagFleetManaged(aIdString, ctx.packageName) { continue } newReferences = append(newReferences, r) @@ -123,7 +122,18 @@ func filterOutSharedTags(ctx *transformationContext, references []interface{}) ( return newReferences, nil } -func filterOutFleetManagedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { +// isSharedTag checks if the given tag ID is a shared tag for the specified package. +// Shared tags ids are created by fleet form the tags.yml file in the package. +// https://github.com/elastic/kibana/blob/5385f96a132114362b2542e6a44c96a697b66c28/x-pack/platform/plugins/shared/fleet/server/services/epm/kibana/assets/tag_assets.ts#L67 +func isSharedTag(aId string, packageName string) bool { + defaultSharedTagTemplate := "fleet-shared-tag-%s" + securitySolutionTagTemplate := "%s-security-solution" + + return strings.Contains(aId, fmt.Sprintf(defaultSharedTagTemplate, packageName)) || + strings.Contains(aId, fmt.Sprintf(securitySolutionTagTemplate, packageName)) +} + +func filterOutSharedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) { newReferences := make([]interface{}, 0) for _, r := range references { reference := r.(map[string]interface{}) @@ -137,17 +147,11 @@ func filterOutFleetManagedTags(ctx *transformationContext, references []interfac continue } - aId, ok := reference["id"] - if !ok { - newReferences = append(newReferences, r) - continue - } - - aIdString, ok := aId.(string) + aId, ok := reference["id"].(string) if !ok { - return nil, fmt.Errorf("failed to assert id as a string: %v", aId) + return nil, fmt.Errorf("failed to assert name as a string: %v", reference["id"]) } - if isTagFleetManaged(aIdString, ctx.packageName) { + if isSharedTag(aId, ctx.packageName) { continue } newReferences = append(newReferences, r) diff --git a/internal/export/transform_remove_fleet_managed_tags_test.go b/internal/export/tags_test.go similarity index 82% rename from internal/export/transform_remove_fleet_managed_tags_test.go rename to internal/export/tags_test.go index 4661c512cf..e453e50850 100644 --- a/internal/export/transform_remove_fleet_managed_tags_test.go +++ b/internal/export/tags_test.go @@ -27,7 +27,7 @@ func TestRemoveFleetTagsDashboard(t *testing.T) { packageName: "elastic_package_registry", } - result, err := removeFleetManagedTags(ctx, given) + result, err := removeFleetTags(ctx, given) require.NoError(t, err) resultJson, err := json.MarshalIndent(&result, "", " ") @@ -55,6 +55,16 @@ func TestRemoveFleetTagsObjects(t *testing.T) { objectFile: "./testdata/elastic_package_registry.random_tag.json", expectedRemoved: false, }, + { + title: "Shared tag - default", + objectFile: "./testdata/elastic_package_registry.shared_tag_default.json", + expectedRemoved: true, + }, + { + title: "Shared tag - security solution", + objectFile: "./testdata/elastic_package_registry.shared_tag_security_solution.json", + expectedRemoved: true, + }, } for _, c := range cases { @@ -70,7 +80,7 @@ func TestRemoveFleetTagsObjects(t *testing.T) { packageName: "elastic_package_registry", } - result, err := removeFleetManagedTags(ctx, given) + result, err := removeFleetTags(ctx, given) require.NoError(t, err) if c.expectedRemoved { diff --git a/internal/export/testdata/elastic_package_registry.dashboard.json b/internal/export/testdata/elastic_package_registry.dashboard.json index 7ee225d009..e01cc91ca9 100644 --- a/internal/export/testdata/elastic_package_registry.dashboard.json +++ b/internal/export/testdata/elastic_package_registry.dashboard.json @@ -68,7 +68,17 @@ "id": "elastic_package_registry-54252400-d309-11ed-97de-b7062e02194f", "name": "tag-ref-54252400-d309-11ed-97de-b7062e02194f", "type": "tag" - } + }, + { + "id": "elastic_package_registry-fleet-shared-tag-elastic_package_registry", + "name": "tag-ref-fleet-shared-tag-default", + "type": "tag" + }, + { + "id": "elastic_package_registry-security-solution-default", + "name": "tag-ref-security-solution-default", + "type": "tag" + } ], "type": "dashboard" } \ No newline at end of file diff --git a/internal/export/testdata/elastic_package_registry.shared_tag_default.json b/internal/export/testdata/elastic_package_registry.shared_tag_default.json new file mode 100644 index 0000000000..623550e44f --- /dev/null +++ b/internal/export/testdata/elastic_package_registry.shared_tag_default.json @@ -0,0 +1,15 @@ +{ + "attributes": { + "color": "#FFFFFF", + "description": "", + "name": "Managed" + }, + "coreMigrationVersion": "8.7.0", + "created_at": "2023-04-04T16:50:46.882Z", + "id": "elastic_package_registry-fleet-shared-tag-elastic_package_registry-default", + "migrationVersion": { + "tag": "8.0.0" + }, + "references": [], + "type": "tag" +} \ No newline at end of file diff --git a/internal/export/testdata/elastic_package_registry.shared_tag_security_solution.json b/internal/export/testdata/elastic_package_registry.shared_tag_security_solution.json new file mode 100644 index 0000000000..487ef1e05a --- /dev/null +++ b/internal/export/testdata/elastic_package_registry.shared_tag_security_solution.json @@ -0,0 +1,15 @@ +{ + "attributes": { + "color": "#FFFFFF", + "description": "", + "name": "Managed" + }, + "coreMigrationVersion": "8.7.0", + "created_at": "2023-04-04T16:50:46.882Z", + "id": "elastic_package_registry-security-solution-default", + "migrationVersion": { + "tag": "8.0.0" + }, + "references": [], + "type": "tag" +} \ No newline at end of file diff --git a/internal/export/transform_remove_shared_tags.go b/internal/export/transform_remove_shared_tags.go deleted file mode 100644 index acb9560160..0000000000 --- a/internal/export/transform_remove_shared_tags.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package export - -import ( - "fmt" - - "github.com/elastic/elastic-package/internal/common" -) - -func removeDuplicateSharedTags(ctx *transformationContext, object common.MapStr) (common.MapStr, error) { - aType, err := object.GetValue("type") - if err != nil { - return nil, fmt.Errorf("failed to read type field: %w", err) - } - - if aType != "tag" { - return object, nil - } - tagId, err := object.GetValue("id") - if err != nil { - return nil, fmt.Errorf("failed to read id field: %w", err) - } - - tagIdStr, ok := tagId.(string) - if !ok { - return nil, fmt.Errorf("failed to convert tag id to string") - } - if isSharedTag(tagIdStr, ctx.packageName) { - return nil, nil - } - return object, nil -} diff --git a/internal/export/transform_remove_shared_tags_test.go b/internal/export/transform_remove_shared_tags_test.go deleted file mode 100644 index eb32712394..0000000000 --- a/internal/export/transform_remove_shared_tags_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package export - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/elastic/elastic-package/internal/common" -) - -func TestRemoveDuplicateSharedTags(t *testing.T) { - tests := []struct { - name string - inputObject common.MapStr - expectedObject common.MapStr - }{ - { - name: "Is default shared tag", - inputObject: map[string]interface{}{ - "type": "tag", - "id": "fleet-shared-tag-system-default", - }, - expectedObject: nil, - }, - { - name: "Is security solution shared tag", - inputObject: map[string]interface{}{ - "type": "tag", - "id": "system-security-solution-default", - }, - expectedObject: nil, - }, - { - name: "Is not shared tag", - inputObject: map[string]interface{}{ - "type": "tag", - "id": "unique-tag", - }, - expectedObject: map[string]interface{}{ - "type": "tag", - "id": "unique-tag", - }, - }, - { - name: "Non-tag object", - inputObject: map[string]interface{}{ - "type": "dashboard", - "id": "My Dashboard", - }, - expectedObject: map[string]interface{}{ - "type": "dashboard", - "id": "My Dashboard", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := &transformationContext{} - inputMapStr := tt.inputObject - result, err := removeDuplicateSharedTags(ctx, inputMapStr) - require.NoError(t, err) - - assert.Equal(t, tt.expectedObject, result) - }) - } -}