Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions internal/controller/apiresourceschema/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ package apiresourceschema

import (
"context"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"strings"
Expand All @@ -29,6 +26,7 @@ import (
"go.uber.org/zap"

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

checksum := hex.EncodeToString(hash.Sum(nil))
checksum := crypto.Hash(crd.Spec.Names)

// include a leading "v" to prevent SHA-1 hashes with digits to break the name
return fmt.Sprintf("v%s.%s.%s", checksum[:8], crd.Spec.Names.Plural, apiGroup)
Expand Down
51 changes: 51 additions & 0 deletions internal/crypto/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2025 The KCP Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package crypto

import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
)

func Hash(data any) string {
hash := sha1.New()

var err error
switch asserted := data.(type) {
case string:
_, err = hash.Write([]byte(asserted))
case []byte:
_, err = hash.Write(asserted)
default:
err = json.NewEncoder(hash).Encode(data)
}

if err != nil {
// This is not something that should ever happen at runtime and is also not
// something we can really gracefully handle, so crashing and restarting might
// be a good way to signal the service owner that something is up.
panic(fmt.Sprintf("Failed to hash: %v", err))
}

return hex.EncodeToString(hash.Sum(nil))
}

func ShortHash(data any) string {
return Hash(data)[:20]
}
21 changes: 3 additions & 18 deletions internal/projection/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ limitations under the License.
package projection

import (
"crypto/sha1"
"encoding/hex"
"fmt"
"strings"

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

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

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

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

return result
}

func shortSha1Hash(value string) string {
hash := sha1.New()
if _, err := hash.Write([]byte(value)); err != nil {
// This is not something that should ever happen at runtime and is also not
// something we can really gracefully handle, so crashing and restarting might
// be a good way to signal the service owner that something is up.
panic(fmt.Sprintf("Failed to hash string: %v", err))
}

encoded := hex.EncodeToString(hash.Sum(nil))

return encoded[:20]
}
39 changes: 23 additions & 16 deletions internal/sync/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"github.com/kcp-dev/logicalcluster/v3"
"go.uber.org/zap"

"github.com/kcp-dev/api-syncagent/internal/crypto"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -56,7 +58,7 @@ func ensureAnnotations(obj metav1.Object, desiredAnnotations map[string]string)
}

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

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

func (k objectKey) Key() string {
result := k.Name
if k.Namespace != "" {
result = k.Namespace + "_" + result
}
if k.ClusterName != "" {
result = string(k.ClusterName) + "_" + result
}

return result
return crypto.Hash(k)
}

func (k objectKey) Labels() labels.Set {
return labels.Set{
remoteObjectClusterLabel: string(k.ClusterName),
remoteObjectNamespaceLabel: k.Namespace,
remoteObjectNameLabel: k.Name,
// Name and namespace can be more than 63 characters long, so we must hash them
// to turn them into valid label values. The full, original value is kept as an annotation.
s := labels.Set{
remoteObjectClusterLabel: string(k.ClusterName),
remoteObjectNameHashLabel: crypto.Hash(k.Name),
}

if k.Namespace != "" {
s[remoteObjectNamespaceHashLabel] = crypto.Hash(k.Namespace)
}

return s
}

func (k objectKey) Annotations() labels.Set {
s := labels.Set{}
s := labels.Set{
remoteObjectNameAnnotation: k.Name,
}

if k.Namespace != "" {
s[remoteObjectNamespaceAnnotation] = k.Namespace
}

if !k.WorkspacePath.Empty() {
s[remoteObjectWorkspacePathAnnotation] = k.WorkspacePath.String()
Expand Down
Loading