Skip to content

Commit 187fa93

Browse files
authored
Merge pull request #40 from kcp-dev/handle-long-names
🐛 Handle long object names
2 parents 5c1c7e3 + 0eb5aea commit 187fa93

File tree

12 files changed

+527
-214
lines changed

12 files changed

+527
-214
lines changed

internal/controller/apiresourceschema/controller.go

+2-12
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ package apiresourceschema
1818

1919
import (
2020
"context"
21-
"crypto/sha1"
22-
"encoding/hex"
23-
"encoding/json"
2421
"fmt"
2522
"reflect"
2623
"strings"
@@ -29,6 +26,7 @@ import (
2926
"go.uber.org/zap"
3027

3128
"github.com/kcp-dev/api-syncagent/internal/controllerutil/predicate"
29+
"github.com/kcp-dev/api-syncagent/internal/crypto"
3230
"github.com/kcp-dev/api-syncagent/internal/discovery"
3331
"github.com/kcp-dev/api-syncagent/internal/projection"
3432
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
@@ -255,15 +253,7 @@ func (r *Reconciler) applyProjection(apiGroup string, crd *apiextensionsv1.Custo
255253
// getAPIResourceSchemaName generates the name for the ARS in kcp. Note that
256254
// kcp requires, just like CRDs, that ARS are named following a specific pattern.
257255
func (r *Reconciler) getAPIResourceSchemaName(apiGroup string, crd *apiextensionsv1.CustomResourceDefinition) string {
258-
hash := sha1.New()
259-
if err := json.NewEncoder(hash).Encode(crd.Spec.Names); err != nil {
260-
// This is not something that should ever happen at runtime and is also not
261-
// something we can really gracefully handle, so crashing and restarting might
262-
// be a good way to signal the service owner that something is up.
263-
panic(fmt.Sprintf("Failed to hash PublishedResource source: %v", err))
264-
}
265-
266-
checksum := hex.EncodeToString(hash.Sum(nil))
256+
checksum := crypto.Hash(crd.Spec.Names)
267257

268258
// include a leading "v" to prevent SHA-1 hashes with digits to break the name
269259
return fmt.Sprintf("v%s.%s.%s", checksum[:8], crd.Spec.Names.Plural, apiGroup)

internal/crypto/hash.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crypto
18+
19+
import (
20+
"crypto/sha1"
21+
"encoding/hex"
22+
"encoding/json"
23+
"fmt"
24+
)
25+
26+
func Hash(data any) string {
27+
hash := sha1.New()
28+
29+
var err error
30+
switch asserted := data.(type) {
31+
case string:
32+
_, err = hash.Write([]byte(asserted))
33+
case []byte:
34+
_, err = hash.Write(asserted)
35+
default:
36+
err = json.NewEncoder(hash).Encode(data)
37+
}
38+
39+
if err != nil {
40+
// This is not something that should ever happen at runtime and is also not
41+
// something we can really gracefully handle, so crashing and restarting might
42+
// be a good way to signal the service owner that something is up.
43+
panic(fmt.Sprintf("Failed to hash: %v", err))
44+
}
45+
46+
return hex.EncodeToString(hash.Sum(nil))
47+
}
48+
49+
func ShortHash(data any) string {
50+
return Hash(data)[:20]
51+
}

internal/projection/naming.go

+3-18
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ limitations under the License.
1717
package projection
1818

1919
import (
20-
"crypto/sha1"
21-
"encoding/hex"
2220
"fmt"
2321
"strings"
2422

2523
"github.com/kcp-dev/logicalcluster/v3"
2624

25+
"github.com/kcp-dev/api-syncagent/internal/crypto"
2726
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
2827

2928
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -44,9 +43,9 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object met
4443
replacer := strings.NewReplacer(
4544
// order of elements is important here, "$fooHash" needs to be defined before "$foo"
4645
syncagentv1alpha1.PlaceholderRemoteClusterName, clusterName.String(),
47-
syncagentv1alpha1.PlaceholderRemoteNamespaceHash, shortSha1Hash(object.GetNamespace()),
46+
syncagentv1alpha1.PlaceholderRemoteNamespaceHash, crypto.ShortHash(object.GetNamespace()),
4847
syncagentv1alpha1.PlaceholderRemoteNamespace, object.GetNamespace(),
49-
syncagentv1alpha1.PlaceholderRemoteNameHash, shortSha1Hash(object.GetName()),
48+
syncagentv1alpha1.PlaceholderRemoteNameHash, crypto.ShortHash(object.GetName()),
5049
syncagentv1alpha1.PlaceholderRemoteName, object.GetName(),
5150
)
5251

@@ -68,17 +67,3 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object met
6867

6968
return result
7069
}
71-
72-
func shortSha1Hash(value string) string {
73-
hash := sha1.New()
74-
if _, err := hash.Write([]byte(value)); err != nil {
75-
// This is not something that should ever happen at runtime and is also not
76-
// something we can really gracefully handle, so crashing and restarting might
77-
// be a good way to signal the service owner that something is up.
78-
panic(fmt.Sprintf("Failed to hash string: %v", err))
79-
}
80-
81-
encoded := hex.EncodeToString(hash.Sum(nil))
82-
83-
return encoded[:20]
84-
}

internal/sync/meta.go

+23-16
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"github.com/kcp-dev/logicalcluster/v3"
2323
"go.uber.org/zap"
2424

25+
"github.com/kcp-dev/api-syncagent/internal/crypto"
26+
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2729
"k8s.io/apimachinery/pkg/labels"
@@ -56,7 +58,7 @@ func ensureAnnotations(obj metav1.Object, desiredAnnotations map[string]string)
5658
}
5759

5860
func ensureFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlruntimeclient.Client, obj *unstructured.Unstructured, finalizer string) (updated bool, err error) {
59-
finalizers := sets.New[string](obj.GetFinalizers()...)
61+
finalizers := sets.New(obj.GetFinalizers()...)
6062
if finalizers.Has(deletionFinalizer) {
6163
return false, nil
6264
}
@@ -75,7 +77,7 @@ func ensureFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlrun
7577
}
7678

7779
func removeFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlruntimeclient.Client, obj *unstructured.Unstructured, finalizer string) (updated bool, err error) {
78-
finalizers := sets.New[string](obj.GetFinalizers()...)
80+
finalizers := sets.New(obj.GetFinalizers()...)
7981
if !finalizers.Has(deletionFinalizer) {
8082
return false, nil
8183
}
@@ -122,27 +124,32 @@ func (k objectKey) String() string {
122124
}
123125

124126
func (k objectKey) Key() string {
125-
result := k.Name
126-
if k.Namespace != "" {
127-
result = k.Namespace + "_" + result
128-
}
129-
if k.ClusterName != "" {
130-
result = string(k.ClusterName) + "_" + result
131-
}
132-
133-
return result
127+
return crypto.Hash(k)
134128
}
135129

136130
func (k objectKey) Labels() labels.Set {
137-
return labels.Set{
138-
remoteObjectClusterLabel: string(k.ClusterName),
139-
remoteObjectNamespaceLabel: k.Namespace,
140-
remoteObjectNameLabel: k.Name,
131+
// Name and namespace can be more than 63 characters long, so we must hash them
132+
// to turn them into valid label values. The full, original value is kept as an annotation.
133+
s := labels.Set{
134+
remoteObjectClusterLabel: string(k.ClusterName),
135+
remoteObjectNameHashLabel: crypto.Hash(k.Name),
141136
}
137+
138+
if k.Namespace != "" {
139+
s[remoteObjectNamespaceHashLabel] = crypto.Hash(k.Namespace)
140+
}
141+
142+
return s
142143
}
143144

144145
func (k objectKey) Annotations() labels.Set {
145-
s := labels.Set{}
146+
s := labels.Set{
147+
remoteObjectNameAnnotation: k.Name,
148+
}
149+
150+
if k.Namespace != "" {
151+
s[remoteObjectNamespaceAnnotation] = k.Namespace
152+
}
146153

147154
if !k.WorkspacePath.Empty() {
148155
s[remoteObjectWorkspacePathAnnotation] = k.WorkspacePath.String()

0 commit comments

Comments
 (0)