Skip to content

Commit 5abc0d4

Browse files
committed
implement shared tags functionality to avoid duplicates in exports
1 parent c9dcd6b commit 5abc0d4

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

internal/export/dashboards.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ package export
77
import (
88
"context"
99
"encoding/json"
10+
"errors"
1011
"fmt"
1112
"os"
1213
"path/filepath"
1314

1415
"github.com/Masterminds/semver/v3"
16+
"github.com/stretchr/testify/assert/yaml"
1517

1618
"github.com/elastic/elastic-package/internal/common"
1719
"github.com/elastic/elastic-package/internal/kibana"
@@ -46,8 +48,14 @@ func Dashboards(ctx context.Context, kibanaClient *kibana.Client, dashboardsIDs
4648
return fmt.Errorf("exporting dashboards using Kibana client failed: %w", err)
4749
}
4850

51+
sharedTags, err := readSharedTagsFile(packageRoot)
52+
if err != nil {
53+
return fmt.Errorf("reading shared tags file failed: %w", err)
54+
}
55+
4956
transformContext := &transformationContext{
5057
packageName: m.Name,
58+
sharedTags: sharedTags,
5159
}
5260

5361
objects, err = applyTransformations(transformContext, objects)
@@ -87,6 +95,7 @@ func applyTransformations(ctx *transformationContext, objects []common.MapStr) (
8795
stripObjectProperties,
8896
standardizeObjectProperties,
8997
removeFleetManagedTags,
98+
removeDuplicateSharedTags,
9099
standardizeObjectID).
91100
transform(objects)
92101
}
@@ -120,3 +129,26 @@ func saveObjectsToFiles(packageRoot string, objects []common.MapStr) error {
120129
}
121130
return nil
122131
}
132+
133+
func readSharedTagsFile(packageRoot string) ([]string, error) {
134+
b, err := os.ReadFile(filepath.Join(packageRoot, "kibana", "tags.yml"))
135+
if err != nil {
136+
if errors.Is(err, os.ErrNotExist) {
137+
// No shared tags file, return empty list
138+
return nil, nil
139+
}
140+
return nil, fmt.Errorf("reading shared tags file failed: %w", err)
141+
}
142+
var sharedTags []struct {
143+
Text string `yaml:"text"`
144+
}
145+
err = yaml.Unmarshal(b, &sharedTags)
146+
if err != nil {
147+
return nil, fmt.Errorf("unmarshalling shared tags file failed: %w", err)
148+
}
149+
tags := make([]string, 0, len(sharedTags))
150+
for _, tag := range sharedTags {
151+
tags = append(tags, tag.Text)
152+
}
153+
return tags, nil
154+
}

internal/export/dashboards_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package export
77
import (
88
"encoding/json"
99
"os"
10+
"path/filepath"
1011
"testing"
1112

1213
"github.com/stretchr/testify/require"
@@ -38,3 +39,45 @@ func TestTransform(t *testing.T) {
3839

3940
require.Equal(t, string(expected), string(result))
4041
}
42+
43+
func TestReadSharedTagsFile(t *testing.T) {
44+
45+
t.Run("file exists", func(t *testing.T) {
46+
tmpDir := t.TempDir()
47+
48+
err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755)
49+
require.NoError(t, err)
50+
err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1
51+
- text: tag2
52+
- text: tag3
53+
`), 0644)
54+
require.NoError(t, err)
55+
56+
tags, err := readSharedTagsFile(tmpDir)
57+
require.NoError(t, err)
58+
require.Equal(t, []string{"tag1", "tag2", "tag3"}, tags)
59+
})
60+
61+
t.Run("file does not exist", func(t *testing.T) {
62+
tmpDir := t.TempDir()
63+
64+
tags, err := readSharedTagsFile(tmpDir)
65+
require.NoError(t, err)
66+
require.Empty(t, tags)
67+
})
68+
69+
t.Run("invalid YAML", func(t *testing.T) {
70+
tmpDir := t.TempDir()
71+
72+
err := os.MkdirAll(filepath.Join(tmpDir, "kibana"), 0755)
73+
require.NoError(t, err)
74+
err = os.WriteFile(filepath.Join(tmpDir, "kibana", "tags.yml"), []byte(`- text: tag1
75+
- text
76+
- text: tag3
77+
`), 0644)
78+
require.NoError(t, err)
79+
80+
_, err = readSharedTagsFile(tmpDir)
81+
require.Error(t, err)
82+
})
83+
}

internal/export/object_transformer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type objectTransformer struct {
1717

1818
type transformationContext struct {
1919
packageName string
20+
sharedTags []string
2021
}
2122

2223
func newObjectTransformer() *objectTransformer {

internal/export/transform_remove_fleet_managed_tags.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package export
66

77
import (
88
"fmt"
9+
"strings"
910

1011
"github.com/elastic/elastic-package/internal/common"
1112
)
@@ -38,6 +39,11 @@ func removeTagReferences(ctx *transformationContext, object common.MapStr) (comm
3839
return nil, err
3940
}
4041

42+
newReferences, err = filterOutSharedTags(ctx, newReferences)
43+
if err != nil {
44+
return nil, err
45+
}
46+
4147
_, err = object.Put("references", newReferences)
4248
if err != nil {
4349
return nil, fmt.Errorf("can't update references: %w", err)
@@ -80,6 +86,42 @@ func isTagFleetManaged(aId, packageName string) bool {
8086
return ok
8187
}
8288

89+
func isSharedTag(aId string, sharedTags []string) bool {
90+
for _, tag := range sharedTags {
91+
id := fmt.Sprintf("tag-ref-%s-default", strings.ReplaceAll(strings.ToLower(tag), " ", "-"))
92+
if aId == id {
93+
return true
94+
}
95+
}
96+
return false
97+
}
98+
99+
func filterOutSharedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) {
100+
newReferences := make([]interface{}, 0)
101+
for _, r := range references {
102+
reference := r.(map[string]interface{})
103+
104+
aType, ok := reference["type"]
105+
if !ok {
106+
continue
107+
}
108+
if aType != "tag" {
109+
newReferences = append(newReferences, r)
110+
continue
111+
}
112+
113+
aNameString, ok := reference["name"].(string)
114+
if !ok {
115+
return nil, fmt.Errorf("failed to assert name as a string: %v", reference["name"])
116+
}
117+
if isSharedTag(aNameString, ctx.sharedTags) {
118+
continue
119+
}
120+
newReferences = append(newReferences, r)
121+
}
122+
return newReferences, nil
123+
}
124+
83125
func filterOutFleetManagedTags(ctx *transformationContext, references []interface{}) ([]interface{}, error) {
84126
newReferences := make([]interface{}, 0)
85127
for _, r := range references {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package export
6+
7+
import (
8+
"fmt"
9+
"slices"
10+
11+
"github.com/elastic/elastic-package/internal/common"
12+
)
13+
14+
func removeDuplicateSharedTags(ctx *transformationContext, object common.MapStr) (common.MapStr, error) {
15+
aType, err := object.GetValue("type")
16+
if err != nil {
17+
return nil, fmt.Errorf("failed to read type field: %w", err)
18+
}
19+
20+
if aType != "tag" {
21+
return object, nil
22+
}
23+
tagName, err := object.GetValue("attributes.name")
24+
if err != nil {
25+
return nil, fmt.Errorf("failed to read attributes.name field: %w", err)
26+
}
27+
28+
tagNameStr, ok := tagName.(string)
29+
if !ok {
30+
return nil, fmt.Errorf("failed to convert tag name to string")
31+
}
32+
if slices.Contains(ctx.sharedTags, tagNameStr) {
33+
// Tag is already in the shared tags list, remove it
34+
return nil, nil
35+
}
36+
return object, nil
37+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package export
6+
7+
import (
8+
"testing"
9+
10+
"github.com/elastic/elastic-package/internal/common"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestRemoveDuplicateSharedTags(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
sharedTags []string
19+
inputObject common.MapStr
20+
expectedObject common.MapStr
21+
}{
22+
{
23+
name: "Tag already in shared tags",
24+
sharedTags: []string{"shared-tag-1", "shared-tag-2"},
25+
inputObject: map[string]interface{}{
26+
"type": "tag",
27+
"attributes": map[string]interface{}{"name": "shared-tag-1"},
28+
},
29+
expectedObject: nil,
30+
},
31+
{
32+
name: "Tag not in shared tags",
33+
sharedTags: []string{"shared-tag-1", "shared-tag-2"},
34+
inputObject: map[string]interface{}{
35+
"type": "tag",
36+
"attributes": map[string]interface{}{"name": "unique-tag"},
37+
},
38+
expectedObject: map[string]interface{}{
39+
"type": "tag",
40+
"attributes": map[string]interface{}{"name": "unique-tag"},
41+
},
42+
},
43+
{
44+
name: "Non-tag object",
45+
sharedTags: []string{"shared-tag-1", "shared-tag-2"},
46+
inputObject: map[string]interface{}{
47+
"type": "dashboard",
48+
"attributes": map[string]interface{}{"title": "My Dashboard"},
49+
},
50+
expectedObject: map[string]interface{}{
51+
"type": "dashboard",
52+
"attributes": map[string]interface{}{"title": "My Dashboard"},
53+
},
54+
},
55+
}
56+
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
ctx := &transformationContext{
60+
sharedTags: tt.sharedTags,
61+
}
62+
inputMapStr := tt.inputObject
63+
result, err := removeDuplicateSharedTags(ctx, inputMapStr)
64+
require.NoError(t, err)
65+
66+
assert.Equal(t, tt.expectedObject, result)
67+
})
68+
}
69+
}

0 commit comments

Comments
 (0)