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
30 changes: 18 additions & 12 deletions internal/persistence/aivencredentials/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package aivencredentials

import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"hash/crc32"
"regexp"
"strconv"
"strings"
Expand All @@ -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"
Expand Down Expand Up @@ -99,19 +101,22 @@ func createCredentials(ctx context.Context, req credentialRequest) (any, error)
}

func CreateOpenSearchCredentials(ctx context.Context, input CreateOpenSearchCredentialsInput) (*CreateOpenSearchCredentialsPayload, error) {
// Strip "opensearch-<team>-" prefix if the user provided the full Kubernetes resource name.
// The buildSpec already prepends "opensearch-<namespace>-" 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,
},
Expand Down Expand Up @@ -145,21 +150,24 @@ func valkeyEnvVarSuffix(instanceName string) string {
}

func CreateValkeyCredentials(ctx context.Context, input CreateValkeyCredentialsInput) (*CreateValkeyCredentialsPayload, error) {
suffix := valkeyEnvVarSuffix(input.InstanceName)
// Strip "valkey-<team>-" prefix if the user provided the full Kubernetes resource name.
// Aivenator expects the short instance name and prepends "valkey-<namespace>-" 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{
"protected": true,
"expiresAt": expiresAt.Format(time.RFC3339),
"valkey": []any{
map[string]any{
"instance": input.InstanceName,
"instance": instanceName,
"access": input.Permission.aivenAccess(),
"secretName": secretName,
},
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions internal/persistence/aivencredentials/queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-<service>-"
prefix := "aiven-" + tt.service + "-"
// Must start with "tmp-<service>-"
prefix := "tmp-" + tt.service + "-"
if len(got) < len(prefix) || got[:len(prefix)] != prefix {
t.Errorf("generateSecretName() = %q, want prefix %q", got, prefix)
}
Expand Down Expand Up @@ -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-<service>-"
prefix := "console-" + tt.service + "-"
// Must start with "tmp-<service>-"
prefix := "tmp-" + tt.service + "-"
if len(got) < len(prefix) || got[:len(prefix)] != prefix {
t.Errorf("generateAppName() = %q, want prefix %q", got, prefix)
}
Expand All @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions internal/persistence/opensearch/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() + "-"
}

Expand Down
4 changes: 2 additions & 2 deletions internal/persistence/opensearch/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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("")
Expand Down
6 changes: 4 additions & 2 deletions internal/persistence/valkey/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions internal/persistence/valkey/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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{
Expand Down
Loading