diff --git a/internal/persistence/aivencredentials/queries.go b/internal/persistence/aivencredentials/queries.go index ee2029f00..4eeabaef8 100644 --- a/internal/persistence/aivencredentials/queries.go +++ b/internal/persistence/aivencredentials/queries.go @@ -2,9 +2,9 @@ package aivencredentials import ( "context" + "crypto/sha256" "encoding/base64" "fmt" - "hash/crc32" "regexp" "strconv" "strings" @@ -15,6 +15,8 @@ import ( "github.com/nais/api/internal/environmentmapper" "github.com/nais/api/internal/graph/apierror" "github.com/nais/api/internal/kubernetes" + "github.com/nais/api/internal/persistence/opensearch" + "github.com/nais/api/internal/persistence/valkey" "github.com/nais/api/internal/slug" "github.com/sirupsen/logrus" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -99,19 +101,22 @@ func createCredentials(ctx context.Context, req credentialRequest) (any, error) } func CreateOpenSearchCredentials(ctx context.Context, input CreateOpenSearchCredentialsInput) (*CreateOpenSearchCredentialsPayload, error) { + // Strip "opensearch--" prefix if the user provided the full Kubernetes resource name. + // The buildSpec already prepends "opensearch--" for the Aivenator. + instanceName := strings.TrimPrefix(input.InstanceName, opensearch.NamePrefix(input.TeamSlug)) result, err := createCredentials(ctx, credentialRequest{ teamSlug: input.TeamSlug, environmentName: input.EnvironmentName, ttl: input.TTL, service: "opensearch", - instanceName: input.InstanceName, + instanceName: instanceName, permission: input.Permission.String(), buildSpec: func(namespace, secretName string, expiresAt time.Time) map[string]any { return map[string]any{ "protected": true, "expiresAt": expiresAt.Format(time.RFC3339), "openSearch": map[string]any{ - "instance": fmt.Sprintf("opensearch-%s-%s", namespace, input.InstanceName), + "instance": fmt.Sprintf("opensearch-%s-%s", namespace, instanceName), "access": input.Permission.aivenAccess(), "secretName": secretName, }, @@ -145,13 +150,16 @@ func valkeyEnvVarSuffix(instanceName string) string { } func CreateValkeyCredentials(ctx context.Context, input CreateValkeyCredentialsInput) (*CreateValkeyCredentialsPayload, error) { - suffix := valkeyEnvVarSuffix(input.InstanceName) + // Strip "valkey--" prefix if the user provided the full Kubernetes resource name. + // Aivenator expects the short instance name and prepends "valkey--" itself. + instanceName := strings.TrimPrefix(input.InstanceName, valkey.NamePrefix(input.TeamSlug)) + suffix := valkeyEnvVarSuffix(instanceName) result, err := createCredentials(ctx, credentialRequest{ teamSlug: input.TeamSlug, environmentName: input.EnvironmentName, ttl: input.TTL, service: "valkey", - instanceName: input.InstanceName, + instanceName: instanceName, permission: input.Permission.String(), buildSpec: func(namespace, secretName string, expiresAt time.Time) map[string]any { return map[string]any{ @@ -159,7 +167,7 @@ func CreateValkeyCredentials(ctx context.Context, input CreateValkeyCredentialsI "expiresAt": expiresAt.Format(time.RFC3339), "valkey": []any{ map[string]any{ - "instance": input.InstanceName, + "instance": instanceName, "access": input.Permission.aivenAccess(), "secretName": secretName, }, @@ -330,16 +338,14 @@ func secretData(secret *unstructured.Unstructured, log logrus.FieldLogger) map[s // generateSecretName creates a deterministic, short secret name. func generateSecretName(username, namespace, service string) string { - hasher := crc32.NewIEEE() - fmt.Fprintf(hasher, "%s-%s-%s", username, namespace, service) - return fmt.Sprintf("aiven-%s-%08x", service, hasher.Sum32()) + hash := sha256.Sum256([]byte(fmt.Sprintf("%s-%s-%s", username, namespace, service))) + return fmt.Sprintf("tmp-%s-%x", service, hash[:3]) } // generateAppName creates a deterministic AivenApplication name from the user identity. func generateAppName(username, service string) string { - hasher := crc32.NewIEEE() - fmt.Fprintf(hasher, "%s-%s", username, service) - return fmt.Sprintf("console-%s-%08x", service, hasher.Sum32()) + hash := sha256.Sum256([]byte(fmt.Sprintf("%s-%s", username, service))) + return fmt.Sprintf("tmp-%s-%x", service, hash[:3]) } // parseTTL parses a human-readable TTL string (e.g. "1d", "7d", "24h") into a time.Duration. diff --git a/internal/persistence/aivencredentials/queries_test.go b/internal/persistence/aivencredentials/queries_test.go index d86ef556e..affab516d 100644 --- a/internal/persistence/aivencredentials/queries_test.go +++ b/internal/persistence/aivencredentials/queries_test.go @@ -75,8 +75,8 @@ func TestGenerateSecretName(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := generateSecretName(tt.username, tt.namespace, tt.service) - // Must start with "aiven--" - prefix := "aiven-" + tt.service + "-" + // Must start with "tmp--" + prefix := "tmp-" + tt.service + "-" if len(got) < len(prefix) || got[:len(prefix)] != prefix { t.Errorf("generateSecretName() = %q, want prefix %q", got, prefix) } @@ -112,8 +112,8 @@ func TestGenerateAppName(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := generateAppName(tt.username, tt.service) - // Must start with "console--" - prefix := "console-" + tt.service + "-" + // Must start with "tmp--" + prefix := "tmp-" + tt.service + "-" if len(got) < len(prefix) || got[:len(prefix)] != prefix { t.Errorf("generateAppName() = %q, want prefix %q", got, prefix) } @@ -136,7 +136,7 @@ func TestGenerateAppName(t *testing.T) { // Verify dead code was removed: generateAppName should NOT contain username // (old bug had ReplaceAll on name but then didn't use it) got := generateAppName("user.name@example.com", "opensearch") - prefix := "console-opensearch-" + prefix := "tmp-opensearch-" if len(got) < len(prefix) || got[:len(prefix)] != prefix { t.Errorf("generateAppName() = %q, want prefix %q", got, prefix) } diff --git a/internal/persistence/opensearch/client.go b/internal/persistence/opensearch/client.go index f31967f7f..344baf330 100644 --- a/internal/persistence/opensearch/client.go +++ b/internal/persistence/opensearch/client.go @@ -15,10 +15,12 @@ type client struct { } func instanceNamer(teamSlug slug.Slug, instanceName string) string { - return namePrefix(teamSlug) + instanceName + return NamePrefix(teamSlug) + instanceName } -func namePrefix(teamSlug slug.Slug) string { +// NamePrefix returns the Kubernetes resource name prefix for OpenSearch instances +// belonging to the given team (e.g. "opensearch-myteam-"). +func NamePrefix(teamSlug slug.Slug) string { return "opensearch-" + teamSlug.String() + "-" } diff --git a/internal/persistence/opensearch/models.go b/internal/persistence/opensearch/models.go index 62c9d481e..f69128ad3 100644 --- a/internal/persistence/opensearch/models.go +++ b/internal/persistence/opensearch/models.go @@ -58,7 +58,7 @@ func (r *OpenSearch) GetNamespace() string { return r.TeamSlug.String() } func (r *OpenSearch) GetLabels() map[string]string { return nil } func (r *OpenSearch) FullyQualifiedName() string { - if strings.HasPrefix(r.Name, namePrefix(r.TeamSlug)) { + if strings.HasPrefix(r.Name, NamePrefix(r.TeamSlug)) { return r.Name } return instanceNamer(r.TeamSlug, r.Name) @@ -169,7 +169,7 @@ func toOpenSearch(u *unstructured.Unstructured, envName string) (*OpenSearch, er name := obj.Name if kubernetes.HasManagedByConsoleLabel(obj) { - name = strings.TrimPrefix(obj.GetName(), namePrefix(slug.Slug(obj.GetNamespace()))) + name = strings.TrimPrefix(obj.GetName(), NamePrefix(slug.Slug(obj.GetNamespace()))) } majorVersion := OpenSearchMajorVersion("") diff --git a/internal/persistence/valkey/client.go b/internal/persistence/valkey/client.go index 2717dd0c5..a565a151b 100644 --- a/internal/persistence/valkey/client.go +++ b/internal/persistence/valkey/client.go @@ -14,12 +14,14 @@ type client struct { watcher *watcher.Watcher[*Valkey] } -func namePrefix(teamSlug slug.Slug) string { +// NamePrefix returns the Kubernetes resource name prefix for Valkey instances +// belonging to the given team (e.g. "valkey-myteam-"). +func NamePrefix(teamSlug slug.Slug) string { return "valkey-" + teamSlug.String() + "-" } func instanceNamer(teamSlug slug.Slug, instanceName string) string { - return namePrefix(teamSlug) + instanceName + return NamePrefix(teamSlug) + instanceName } func (c client) getAccessForApplications(ctx context.Context, environmentName, valkeyName string, teamSlug slug.Slug) ([]*ValkeyAccess, error) { diff --git a/internal/persistence/valkey/models.go b/internal/persistence/valkey/models.go index cbff58ebc..a81594bf6 100644 --- a/internal/persistence/valkey/models.go +++ b/internal/persistence/valkey/models.go @@ -68,7 +68,7 @@ func (r Valkey) ID() ident.Ident { } func (r Valkey) FullyQualifiedName() string { - if strings.HasPrefix(r.Name, namePrefix(r.TeamSlug)) { + if strings.HasPrefix(r.Name, NamePrefix(r.TeamSlug)) { return r.Name } return instanceNamer(r.TeamSlug, r.Name) @@ -180,7 +180,7 @@ func toValkey(u *unstructured.Unstructured, envName string) (*Valkey, error) { name := obj.Name if kubernetes.HasManagedByConsoleLabel(obj) { - name = strings.TrimPrefix(obj.GetName(), namePrefix(slug.Slug(obj.GetNamespace()))) + name = strings.TrimPrefix(obj.GetName(), NamePrefix(slug.Slug(obj.GetNamespace()))) } return &Valkey{