Skip to content

Commit ac0c633

Browse files
authored
Limit names and labels to 63 characters (open-telemetry#609)
* added naming formaters from jaeger project * updated naming to use name formatters * update labels to conform with kubernetes rules * fixed linting issues * added Jaeger copyright * updated Jaeger copyright
1 parent 72324d1 commit ac0c633

File tree

7 files changed

+291
-17
lines changed

7 files changed

+291
-17
lines changed

pkg/collector/labels.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
package collector
1616

1717
import (
18-
"fmt"
19-
2018
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
19+
"github.com/open-telemetry/opentelemetry-operator/pkg/naming"
2120
)
2221

2322
// Labels return the common labels to all objects that are part of a managed OpenTelemetryCollector.
@@ -31,7 +30,7 @@ func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string {
3130
}
3231

3332
base["app.kubernetes.io/managed-by"] = "opentelemetry-operator"
34-
base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, instance.Name)
33+
base["app.kubernetes.io/instance"] = naming.Truncate("%s.%s", 63, instance.Namespace, instance.Name)
3534
base["app.kubernetes.io/part-of"] = "opentelemetry"
3635
base["app.kubernetes.io/component"] = "opentelemetry-collector"
3736

pkg/naming/dns.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Additional copyrights:
16+
// Copyright The Jaeger Authors
17+
18+
package naming
19+
20+
import (
21+
"regexp"
22+
"strings"
23+
"unicode/utf8"
24+
)
25+
26+
var regex = regexp.MustCompile(`[a-z0-9]`)
27+
28+
// DNSName returns a dns-safe string for the given name.
29+
// Any char that is not [a-z0-9] is replaced by "-" or "a".
30+
// Replacement character "a" is used only at the beginning or at the end of the name.
31+
// The function does not change length of the string.
32+
// source: https://github.com/jaegertracing/jaeger-operator/blob/91e3b69ee5c8761bbda9d3cf431400a73fc1112a/pkg/util/dns_name.go#L15
33+
func DNSName(name string) string {
34+
var d []rune
35+
36+
for i, x := range strings.ToLower(name) {
37+
if regex.Match([]byte(string(x))) {
38+
d = append(d, x)
39+
} else {
40+
if i == 0 || i == utf8.RuneCountInString(name)-1 {
41+
d = append(d, 'a')
42+
} else {
43+
d = append(d, '-')
44+
}
45+
}
46+
}
47+
48+
return string(d)
49+
}

pkg/naming/dns_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Additional copyrights:
16+
// Copyright The Jaeger Authors
17+
18+
package naming
19+
20+
import (
21+
"regexp"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestDnsName(t *testing.T) {
28+
var tests = []struct {
29+
in string
30+
out string
31+
}{
32+
{"simplest", "simplest"},
33+
{"instance.with.dots-collector-headless", "instance-with-dots-collector-headless"},
34+
{"TestQueryDottedServiceName.With.Dots", "testquerydottedservicename-with-dots"},
35+
{"Service🦄", "servicea"},
36+
{"📈Stock-Tracker", "astock-tracker"},
37+
{"-📈Stock-Tracker", "a-stock-tracker"},
38+
{"📈", "a"},
39+
{"foo-", "fooa"},
40+
{"-foo", "afoo"},
41+
}
42+
rule, err := regexp.Compile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
43+
assert.NoError(t, err)
44+
45+
for _, tt := range tests {
46+
assert.Equal(t, tt.out, DNSName(tt.in))
47+
matched := rule.Match([]byte(tt.out))
48+
assert.True(t, matched, "%v is not a valid name", tt.out)
49+
}
50+
}

pkg/naming/main.go

+9-11
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,17 @@
1616
package naming
1717

1818
import (
19-
"fmt"
20-
2119
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
2220
)
2321

2422
// ConfigMap builds the name for the config map used in the OpenTelemetryCollector containers.
2523
func ConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string {
26-
return fmt.Sprintf("%s-collector", otelcol.Name)
24+
return DNSName(Truncate("%s-collector", 63, otelcol.Name))
2725
}
2826

2927
// TAConfigMap returns the name for the config map used in the TargetAllocator.
3028
func TAConfigMap(otelcol v1alpha1.OpenTelemetryCollector) string {
31-
return fmt.Sprintf("%s-targetallocator", otelcol.Name)
29+
return DNSName(Truncate("%s-targetallocator", 63, otelcol.Name))
3230
}
3331

3432
// ConfigMapVolume returns the name to use for the config map's volume in the pod.
@@ -53,35 +51,35 @@ func TAContainer() string {
5351

5452
// Collector builds the collector (deployment/daemonset) name based on the instance.
5553
func Collector(otelcol v1alpha1.OpenTelemetryCollector) string {
56-
return fmt.Sprintf("%s-collector", otelcol.Name)
54+
return DNSName(Truncate("%s-collector", 63, otelcol.Name))
5755
}
5856

5957
// TargetAllocator returns the TargetAllocator deployment resource name.
6058
func TargetAllocator(otelcol v1alpha1.OpenTelemetryCollector) string {
61-
return fmt.Sprintf("%s-targetallocator", otelcol.Name)
59+
return DNSName(Truncate("%s-targetallocator", 63, otelcol.Name))
6260
}
6361

6462
// HeadlessService builds the name for the headless service based on the instance.
6563
func HeadlessService(otelcol v1alpha1.OpenTelemetryCollector) string {
66-
return fmt.Sprintf("%s-headless", Service(otelcol))
64+
return DNSName(Truncate("%s-headless", 63, Service(otelcol)))
6765
}
6866

6967
// MonitoringService builds the name for the monitoring service based on the instance.
7068
func MonitoringService(otelcol v1alpha1.OpenTelemetryCollector) string {
71-
return fmt.Sprintf("%s-monitoring", Service(otelcol))
69+
return DNSName(Truncate("%s-monitoring", 63, Service(otelcol)))
7270
}
7371

7472
// Service builds the service name based on the instance.
7573
func Service(otelcol v1alpha1.OpenTelemetryCollector) string {
76-
return fmt.Sprintf("%s-collector", otelcol.Name)
74+
return DNSName(Truncate("%s-collector", 63, otelcol.Name))
7775
}
7876

7977
// TAService returns the name to use for the TargetAllocator service.
8078
func TAService(otelcol v1alpha1.OpenTelemetryCollector) string {
81-
return fmt.Sprintf("%s-targetallocator", otelcol.Name)
79+
return DNSName(Truncate("%s-targetallocator", 63, otelcol.Name))
8280
}
8381

8482
// ServiceAccount builds the service account name based on the instance.
8583
func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) string {
86-
return fmt.Sprintf("%s-collector", otelcol.Name)
84+
return DNSName(Truncate("%s-collector", 63, otelcol.Name))
8785
}

pkg/naming/triming.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Additional copyrights:
16+
// Copyright The Jaeger Authors
17+
18+
package naming
19+
20+
import (
21+
"fmt"
22+
"regexp"
23+
)
24+
25+
var regexpEndReplace, regexpBeginReplace *regexp.Regexp
26+
27+
func init() {
28+
regexpEndReplace, _ = regexp.Compile("[^A-Za-z0-9]+$")
29+
regexpBeginReplace, _ = regexp.Compile("^[^A-Za-z0-9]+")
30+
}
31+
32+
// Truncate will shorten the length of the instance name so that it contains at most max chars when combined with the fixed part
33+
// If the fixed part is already bigger than the max, this function is noop.
34+
// source: https://github.com/jaegertracing/jaeger-operator/blob/91e3b69ee5c8761bbda9d3cf431400a73fc1112a/pkg/util/truncate.go#L17
35+
func Truncate(format string, max int, values ...interface{}) string {
36+
var truncated []interface{}
37+
result := fmt.Sprintf(format, values...)
38+
39+
if excess := len(result) - max; excess > 0 {
40+
// we try to reduce the first string we find
41+
for _, value := range values {
42+
if excess == 0 {
43+
continue
44+
}
45+
46+
if s, ok := value.(string); ok {
47+
if len(s) > excess {
48+
value = s[:len(s)-excess]
49+
excess = 0
50+
} else {
51+
value = "" // skip this value entirely
52+
excess = excess - len(s)
53+
}
54+
}
55+
56+
truncated = append(truncated, value)
57+
}
58+
59+
result = fmt.Sprintf(format, truncated...)
60+
}
61+
62+
// if at this point, the result is still bigger than max, apply a hard cap:
63+
if len(result) > max {
64+
return result[:max]
65+
}
66+
67+
return trimNonAlphaNumeric(result)
68+
}
69+
70+
// trimNonAlphaNumeric remove all non-alphanumeric values from start and end of the string
71+
// source: https://github.com/jaegertracing/jaeger-operator/blob/91e3b69ee5c8761bbda9d3cf431400a73fc1112a/pkg/util/truncate.go#L53
72+
func trimNonAlphaNumeric(text string) string {
73+
newText := regexpEndReplace.ReplaceAllString(text, "")
74+
return regexpBeginReplace.ReplaceAllString(newText, "")
75+
}

pkg/naming/triming_test.go

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Additional copyrights:
16+
// Copyright The Jaeger Authors
17+
18+
package naming
19+
20+
import (
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
)
25+
26+
func TestTruncate(t *testing.T) {
27+
for _, tt := range []struct {
28+
format string
29+
max int
30+
values []interface{}
31+
expected string
32+
cap string
33+
}{
34+
{
35+
format: "%s-collector",
36+
max: 63,
37+
values: []interface{}{"simplest"},
38+
expected: "simplest-collector",
39+
cap: "the standard case",
40+
},
41+
{
42+
format: "d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174-c85b7644b6b5",
43+
max: 63,
44+
values: []interface{}{},
45+
expected: "d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174-c85",
46+
cap: "first N case",
47+
},
48+
{
49+
format: "%s-collector",
50+
max: 63,
51+
values: []interface{}{"d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174-c85b7644b6b5"},
52+
expected: "d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11e-collector",
53+
cap: "instance + fixed within bounds",
54+
},
55+
{
56+
format: "%s-%s-collector",
57+
max: 63,
58+
values: []interface{}{"d0c1e62", "4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174-c85b7644b6b5"},
59+
expected: "4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174--collector",
60+
cap: "first value gets dropped, second truncated",
61+
},
62+
{
63+
format: "%d-%s-collector",
64+
max: 63,
65+
values: []interface{}{42, "d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96-11ea-b174-c85b7644b6b5"},
66+
expected: "42-d0c1e62-4d96-11ea-b174-c85b7644b6b5-5d0c1e62-4d96--collector",
67+
cap: "first value gets passed, second truncated",
68+
},
69+
} {
70+
assert.Equal(t, tt.expected, Truncate(tt.format, tt.max, tt.values...))
71+
}
72+
}
73+
74+
func TestTrimNonAlphaNumeric(t *testing.T) {
75+
tests := []struct {
76+
input string
77+
expected string
78+
}{
79+
{
80+
input: "-%$#ThisIsALabel",
81+
expected: "ThisIsALabel",
82+
},
83+
84+
{
85+
input: "label-invalid--_truncated-.",
86+
expected: "label-invalid--_truncated",
87+
},
88+
89+
{
90+
input: "--(label-invalid--_truncated-#.1.",
91+
expected: "label-invalid--_truncated-#.1",
92+
},
93+
94+
{
95+
input: "12ValidLabel3",
96+
expected: "12ValidLabel3",
97+
},
98+
}
99+
100+
for _, test := range tests {
101+
output := trimNonAlphaNumeric(test.input)
102+
assert.Equal(t, test.expected, output)
103+
}
104+
}

pkg/targetallocator/labels.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
package targetallocator
1616

1717
import (
18-
"fmt"
19-
2018
"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
19+
"github.com/open-telemetry/opentelemetry-operator/pkg/naming"
2120
)
2221

2322
// Labels return the common labels to all TargetAllocator objects that are part of a managed OpenTelemetryCollector.
@@ -31,7 +30,7 @@ func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string {
3130
}
3231

3332
base["app.kubernetes.io/managed-by"] = "opentelemetry-operator"
34-
base["app.kubernetes.io/instance"] = fmt.Sprintf("%s.%s", instance.Namespace, instance.Name)
33+
base["app.kubernetes.io/instance"] = naming.Truncate("%s.%s", 63, instance.Namespace, instance.Name)
3534
base["app.kubernetes.io/part-of"] = "opentelemetry"
3635
base["app.kubernetes.io/component"] = "opentelemetry-targetallocator"
3736

0 commit comments

Comments
 (0)