From 988b7feffdeace6022eca839aa2606b5b3f255b4 Mon Sep 17 00:00:00 2001 From: zazulam Date: Wed, 26 Aug 2026 08:44:46 -0400 Subject: [PATCH 1/2] Allow common storage prefix subpaths Permit slash-separated storage prefixes whose path segments use common alphanumeric, hyphen, and underscore naming patterns. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 1 + cmd/mimir/config-descriptor.json | 6 ++-- cmd/mimir/help-all.txt.tmpl | 6 ++-- cmd/mimir/help.txt.tmpl | 6 ++-- .../configuration-parameters/index.md | 15 +++++---- pkg/storage/bucket/client.go | 8 ++--- pkg/storage/bucket/client_test.go | 32 +++++++++++++++---- 7 files changed, 48 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 680e760c100..a04398ce79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * [ENHANCEMENT] Store-gateway: added a `route` label to the `cortex_bucket_store_series_request_stage_duration_seconds` metric to match the `route` label of `cortex_request_duration_seconds`. #16374 * [ENHANCEMENT] Store-gateway: Add `force_attempt_http2` option to block storage client config to enable HTTP/2. #16385, #16422 * [ENHANCEMENT] Validation: Add an optional `reason` field to `limited_queries` rules, aligning them with `blocked_queries`. When set, the reason is included in the client-facing error and the query-frontend's `"query limited"` log line. #16407 +* [ENHANCEMENT] Storage: Allow common subpath patterns containing slashes, hyphens, and underscores in storage prefixes. #16479 * [ENHANCEMENT] Compactor: Add the experimental `-compactor.scheduler-client.enable-ring-based-cleanup` option, which when disabled stops a scheduler-mode compactor from running the ring-based background blocks cleaner. #16457 * [FEATURE] Querier: Add experimental per-tenant limit `-querier.max-blocks-per-store-request` to cap the number of blocks a single store-gateway request may reference. Disabled by default. #16292 * [FEATURE] Validation: Add optional `id`, `note`, `created_by`, `created_at`, and `expires_at` fields to `blocked_queries` and `limited_queries` rules, for tooling to attach ownership/context metadata to a rule. For rules with `expires_at` set, the earliest `expires_at` per tenant and `id` (rules without an `id` are grouped together) is exported as the `cortex_blocked_query_rule_expires_at`/`cortex_limited_query_rule_expires_at` metrics, so an alert can fire on stale rules; this is informational only and never affects enforcement. The query-frontend's `"query blocked"` log line now also includes the matched rule's `id` and whether it is expired, and rate-limited queries are now logged with a new `"query limited"` line carrying the same fields. #16395 diff --git a/cmd/mimir/config-descriptor.json b/cmd/mimir/config-descriptor.json index c8ad7e27162..9d34db8654d 100644 --- a/cmd/mimir/config-descriptor.json +++ b/cmd/mimir/config-descriptor.json @@ -11236,7 +11236,7 @@ "kind": "field", "name": "storage_prefix", "required": false, - "desc": "Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters.", + "desc": "Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores.", "fieldValue": null, "fieldDefaultValue": "", "fieldFlag": "blocks-storage.storage-prefix", @@ -18846,7 +18846,7 @@ "kind": "field", "name": "storage_prefix", "required": false, - "desc": "Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters.", + "desc": "Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores.", "fieldValue": null, "fieldDefaultValue": "", "fieldFlag": "ruler-storage.storage-prefix", @@ -21192,7 +21192,7 @@ "kind": "field", "name": "storage_prefix", "required": false, - "desc": "Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters.", + "desc": "Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores.", "fieldValue": null, "fieldDefaultValue": "", "fieldFlag": "alertmanager-storage.storage-prefix", diff --git a/cmd/mimir/help-all.txt.tmpl b/cmd/mimir/help-all.txt.tmpl index 1ffb23fab84..f864b5be04d 100644 --- a/cmd/mimir/help-all.txt.tmpl +++ b/cmd/mimir/help-all.txt.tmpl @@ -150,7 +150,7 @@ Usage of ./cmd/mimir/mimir: -alertmanager-storage.s3.trace.enabled When enabled, low-level S3 HTTP operation information is logged at the debug level. -alertmanager-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -alertmanager-storage.swift.application-credential-id string OpenStack Swift application credential id -alertmanager-storage.swift.application-credential-name string @@ -798,7 +798,7 @@ Usage of ./cmd/mimir/mimir: -blocks-storage.s3.trace.enabled When enabled, low-level S3 HTTP operation information is logged at the debug level. -blocks-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -blocks-storage.swift.application-credential-id string OpenStack Swift application credential id -blocks-storage.swift.application-credential-name string @@ -3308,7 +3308,7 @@ Usage of ./cmd/mimir/mimir: -ruler-storage.s3.trace.enabled When enabled, low-level S3 HTTP operation information is logged at the debug level. -ruler-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -ruler-storage.swift.application-credential-id string OpenStack Swift application credential id -ruler-storage.swift.application-credential-name string diff --git a/cmd/mimir/help.txt.tmpl b/cmd/mimir/help.txt.tmpl index 1ac3c459c83..b3d7b1ff66a 100644 --- a/cmd/mimir/help.txt.tmpl +++ b/cmd/mimir/help.txt.tmpl @@ -42,7 +42,7 @@ Usage of ./cmd/mimir/mimir: -alertmanager-storage.s3.sts-endpoint string Accessing S3 resources using temporary, secure credentials provided by AWS Security Token Service. -alertmanager-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -alertmanager-storage.swift.application-credential-id string OpenStack Swift application credential id -alertmanager-storage.swift.application-credential-name string @@ -198,7 +198,7 @@ Usage of ./cmd/mimir/mimir: -blocks-storage.s3.sts-endpoint string Accessing S3 resources using temporary, secure credentials provided by AWS Security Token Service. -blocks-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -blocks-storage.swift.application-credential-id string OpenStack Swift application credential id -blocks-storage.swift.application-credential-name string @@ -768,7 +768,7 @@ Usage of ./cmd/mimir/mimir: -ruler-storage.s3.sts-endpoint string Accessing S3 resources using temporary, secure credentials provided by AWS Security Token Service. -ruler-storage.storage-prefix string - Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters. + Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores. -ruler-storage.swift.application-credential-id string OpenStack Swift application credential id -ruler-storage.swift.application-credential-name string diff --git a/docs/sources/mimir/configure/configuration-parameters/index.md b/docs/sources/mimir/configure/configuration-parameters/index.md index 1babf8e4145..5c24b545695 100644 --- a/docs/sources/mimir/configure/configuration-parameters/index.md +++ b/docs/sources/mimir/configure/configuration-parameters/index.md @@ -3172,8 +3172,9 @@ The `ruler_storage` block configures the ruler storage backend. # The CLI flags prefix for this block configuration is: ruler-storage [filesystem: ] -# Prefix for all objects stored in the backend storage. For simplicity, it may -# only contain digits and English alphabet letters. +# Prefix for all objects stored in the backend storage. It may only contain +# slash-separated path segments made of digits, English alphabet letters, +# hyphens, and underscores. # CLI flag: -ruler-storage.storage-prefix [storage_prefix: | default = ""] @@ -3583,8 +3584,9 @@ The `alertmanager_storage` block configures the alertmanager storage backend. # The CLI flags prefix for this block configuration is: alertmanager-storage [filesystem: ] -# Prefix for all objects stored in the backend storage. For simplicity, it may -# only contain digits and English alphabet letters. +# Prefix for all objects stored in the backend storage. It may only contain +# slash-separated path segments made of digits, English alphabet letters, +# hyphens, and underscores. # CLI flag: -alertmanager-storage.storage-prefix [storage_prefix: | default = ""] @@ -6085,8 +6087,9 @@ The `blocks_storage` block configures the blocks storage. # The CLI flags prefix for this block configuration is: blocks-storage [filesystem: ] -# Prefix for all objects stored in the backend storage. For simplicity, it may -# only contain digits and English alphabet letters. +# Prefix for all objects stored in the backend storage. It may only contain +# slash-separated path segments made of digits, English alphabet letters, +# hyphens, and underscores. # CLI flag: -blocks-storage.storage-prefix [storage_prefix: | default = ""] diff --git a/pkg/storage/bucket/client.go b/pkg/storage/bucket/client.go index 87b85126827..9f9ca12f420 100644 --- a/pkg/storage/bucket/client.go +++ b/pkg/storage/bucket/client.go @@ -47,8 +47,8 @@ const ( // Filesystem is the value for the filesystem storage backend. Filesystem = "filesystem" - // validPrefixCharactersRegex allows only alphanumeric characters to prevent subtle bugs and simplify validation - validPrefixCharactersRegex = `^[\da-zA-Z]+$` + // validPrefixCharactersRegex allows slash-separated path segments containing alphanumeric characters, hyphens, and underscores. + validPrefixCharactersRegex = `^[\da-zA-Z_-]+(?:/[\da-zA-Z_-]+)*$` // MimirInternalsPrefix is the bucket prefix under which all Mimir internal cluster-wide objects are stored. // The object storage path delimiter (/) is appended to this prefix when building the full object path. @@ -59,7 +59,7 @@ var ( SupportedBackends = []string{S3, GCS, Azure, Swift, Filesystem} ErrUnsupportedStorageBackend = errors.New("unsupported storage backend") - ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain digits and English alphabet letters") + ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores") ) type StorageBackendConfig struct { @@ -142,7 +142,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) { func (cfg *Config) RegisterFlagsWithPrefixAndDefaultDirectory(prefix, dir string, f *flag.FlagSet) { cfg.StorageBackendConfig.RegisterFlagsWithPrefixAndDefaultDirectory(prefix, dir, f) - f.StringVar(&cfg.StoragePrefix, prefix+"storage-prefix", "", "Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters.") + f.StringVar(&cfg.StoragePrefix, prefix+"storage-prefix", "", "Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores.") } func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { diff --git a/pkg/storage/bucket/client_test.go b/pkg/storage/bucket/client_test.go index 1f1f546db99..0b90552139b 100644 --- a/pkg/storage/bucket/client_test.go +++ b/pkg/storage/bucket/client_test.go @@ -209,22 +209,40 @@ func TestClient_ConfigValidation(t *testing.T) { cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld"}, }, { - name: "storage_prefix non-alphanumeric characters", - cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello-world!"}, + name: "storage_prefix with hyphens and underscores", + cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello-world_1"}, + }, + { + name: "storage_prefix with subpaths", + cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "tenant-a/environment_1"}, + }, + { + name: "storage_prefix with other special characters", + cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello.world!"}, expectedError: ErrInvalidCharactersInStoragePrefix, }, { - name: "storage_prefix suffixed with a slash (non-alphanumeric)", + name: "storage_prefix suffixed with a slash", cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "helloworld/"}, expectedError: ErrInvalidCharactersInStoragePrefix, }, { - name: "storage_prefix that has some character strings that have a meaning in unix paths (..)", + name: "storage_prefix prefixed with a slash", + cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "/helloworld"}, + expectedError: ErrInvalidCharactersInStoragePrefix, + }, + { + name: "storage_prefix with repeated slashes", + cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "hello//world"}, + expectedError: ErrInvalidCharactersInStoragePrefix, + }, + { + name: "storage_prefix that is a parent path element", cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: ".."}, expectedError: ErrInvalidCharactersInStoragePrefix, }, { - name: "storage_prefix that has some character strings that have a meaning in unix paths (.)", + name: "storage_prefix that is a current path element", cfg: Config{StorageBackendConfig: StorageBackendConfig{Backend: Filesystem}, StoragePrefix: "."}, expectedError: ErrInvalidCharactersInStoragePrefix, }, @@ -254,7 +272,7 @@ func TestNewPrefixedBucketClient(t *testing.T) { Directory: tempDir, }, }, - StoragePrefix: "prefix", + StoragePrefix: "prefix/nested_directory", } client, err := NewClient(ctx, cfg, "test", test.NewTestingLogger(t), nil) @@ -266,7 +284,7 @@ func TestNewPrefixedBucketClient(t *testing.T) { _, err = client.Get(ctx, "file") assert.NoError(t, err) - filePath := path.Join(tempDir, "prefix", "file") + filePath := path.Join(tempDir, "prefix", "nested_directory", "file") assert.FileExists(t, filePath) b, err := os.ReadFile(filePath) From d337ccfd8ee0038973da8b8a9737b03e360729c3 Mon Sep 17 00:00:00 2001 From: zazulam Date: Wed, 26 Aug 2026 08:53:56 -0400 Subject: [PATCH 2/2] Reject overlapping storage subpaths Prevent parent and child storage prefixes from sharing the blocks object namespace while allowing sibling subpaths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- .../configure-object-storage-backend.md | 2 +- pkg/mimir/mimir.go | 16 ++++-- pkg/mimir/mimir_test.go | 57 +++++++++++++++++++ 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a04398ce79b..55bed28d79a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ * [ENHANCEMENT] Store-gateway: added a `route` label to the `cortex_bucket_store_series_request_stage_duration_seconds` metric to match the `route` label of `cortex_request_duration_seconds`. #16374 * [ENHANCEMENT] Store-gateway: Add `force_attempt_http2` option to block storage client config to enable HTTP/2. #16385, #16422 * [ENHANCEMENT] Validation: Add an optional `reason` field to `limited_queries` rules, aligning them with `blocked_queries`. When set, the reason is included in the client-facing error and the query-frontend's `"query limited"` log line. #16407 -* [ENHANCEMENT] Storage: Allow common subpath patterns containing slashes, hyphens, and underscores in storage prefixes. #16479 +* [ENHANCEMENT] Storage: Allow common subpath patterns containing slashes, hyphens, and underscores in storage prefixes while rejecting overlapping parent and child prefixes. #16479 * [ENHANCEMENT] Compactor: Add the experimental `-compactor.scheduler-client.enable-ring-based-cleanup` option, which when disabled stops a scheduler-mode compactor from running the ring-based background blocks cleaner. #16457 * [FEATURE] Querier: Add experimental per-tenant limit `-querier.max-blocks-per-store-request` to cap the number of blocks a single store-gateway request may reference. Disabled by default. #16292 * [FEATURE] Validation: Add optional `id`, `note`, `created_by`, `created_at`, and `expires_at` fields to `blocked_queries` and `limited_queries` rules, for tooling to attach ownership/context metadata to a rule. For rules with `expires_at` set, the earliest `expires_at` per tenant and `id` (rules without an `id` are grouped together) is exported as the `cortex_blocked_query_rule_expires_at`/`cortex_limited_query_rule_expires_at` metrics, so an alert can fire on stale rules; this is informational only and never affects enforcement. The query-frontend's `"query blocked"` log line now also includes the matched rule's `id` and whether it is expired, and rate-limited queries are now logged with a new `"query limited"` line carrying the same fields. #16395 diff --git a/docs/sources/mimir/configure/configure-object-storage-backend.md b/docs/sources/mimir/configure/configure-object-storage-backend.md index b8af1a9e192..bffdc61f219 100644 --- a/docs/sources/mimir/configure/configure-object-storage-backend.md +++ b/docs/sources/mimir/configure/configure-object-storage-backend.md @@ -48,7 +48,7 @@ When using the common configuration, make [`blocks_storage`](../configuration-pa {{< /admonition >}} -Grafana Mimir will fail to start if you configure blocks storage to use the same bucket and storage prefix that the Alertmanager or ruler store uses. +Grafana Mimir will fail to start if you configure blocks storage to use the same bucket and a storage prefix that overlaps the Alertmanager or ruler storage prefix. Find examples of setting up the different object stores below: diff --git a/pkg/mimir/mimir.go b/pkg/mimir/mimir.go index bacaeea567a..62dca2bcb40 100644 --- a/pkg/mimir/mimir.go +++ b/pkg/mimir/mimir.go @@ -541,24 +541,24 @@ func validateBucketConfig(cfg bucket.Config, blockStorageBucketCfg bucket.Config return nil } - if cfg.StoragePrefix != blockStorageBucketCfg.StoragePrefix { + if !storagePrefixesOverlap(cfg.StoragePrefix, blockStorageBucketCfg.StoragePrefix) { return nil } switch cfg.Backend { case bucket.S3: if cfg.S3.BucketName == blockStorageBucketCfg.S3.BucketName { - return errors.New("S3 bucket name and storage prefix cannot be the same as the one used in blocks storage config") + return errors.New("S3 bucket name and storage prefix cannot overlap with the ones used in blocks storage config") } case bucket.GCS: if cfg.GCS.BucketName == blockStorageBucketCfg.GCS.BucketName { - return errors.New("GCS bucket name and storage prefix cannot be the same as the one used in blocks storage config") + return errors.New("GCS bucket name and storage prefix cannot overlap with the ones used in blocks storage config") } case bucket.Azure: if cfg.Azure.ContainerName == blockStorageBucketCfg.Azure.ContainerName && cfg.Azure.StorageAccountName == blockStorageBucketCfg.Azure.StorageAccountName { - return errors.New("Azure container and account names and storage prefix cannot be the same as the ones used in blocks storage config") + return errors.New("Azure container and account names and storage prefix cannot overlap with the ones used in blocks storage config") } // To keep it simple here we only check that container and project names are not the same. @@ -567,12 +567,18 @@ func validateBucketConfig(cfg bucket.Config, blockStorageBucketCfg bucket.Config // may have several configured endpoints. case bucket.Swift: if cfg.Swift.ContainerName == blockStorageBucketCfg.Swift.ContainerName && cfg.Swift.ProjectName == blockStorageBucketCfg.Swift.ProjectName { - return errors.New("Swift container and project names and storage prefix cannot be the same as the ones used in blocks storage config") + return errors.New("Swift container and project names and storage prefix cannot overlap with the ones used in blocks storage config") } } return nil } +func storagePrefixesOverlap(first, second string) bool { + return first == second || + strings.HasPrefix(first, second+"/") || + strings.HasPrefix(second, first+"/") +} + // validateFilesystemPaths checks all configured filesystem paths and return error if it finds two of them // overlapping (Mimir expects all filesystem paths to not overlap). func (c *Config) validateFilesystemPaths(logger log.Logger) error { diff --git a/pkg/mimir/mimir_test.go b/pkg/mimir/mimir_test.go index 9c6a2ab5def..984f209ed02 100644 --- a/pkg/mimir/mimir_test.go +++ b/pkg/mimir/mimir_test.go @@ -381,6 +381,40 @@ func TestConfigValidation(t *testing.T) { }, expectedError: errInvalidBucketConfig, }, + { + name: "S3: should fail if storage prefixes overlap between alertmanager and blocks storage", + getTestConfig: func() *Config { + cfg := newDefaultConfig() + _ = cfg.Target.Set("all,alertmanager") + + for _, bucketCfg := range []*bucket.Config{&cfg.BlocksStorage.Bucket, &cfg.AlertmanagerStorage.Config} { + bucketCfg.Backend = bucket.S3 + bucketCfg.S3.BucketName = "b1" + bucketCfg.S3.Region = "r1" + } + cfg.BlocksStorage.Bucket.StoragePrefix = "team-a" + cfg.AlertmanagerStorage.StoragePrefix = "team-a/mimir_alertmanager" + return cfg + }, + expectedError: errInvalidBucketConfig, + }, + { + name: "S3: should pass if storage prefixes are sibling subpaths", + getTestConfig: func() *Config { + cfg := newDefaultConfig() + _ = cfg.Target.Set("all,alertmanager") + + for _, bucketCfg := range []*bucket.Config{&cfg.BlocksStorage.Bucket, &cfg.AlertmanagerStorage.Config} { + bucketCfg.Backend = bucket.S3 + bucketCfg.S3.BucketName = "b1" + bucketCfg.S3.Region = "r1" + } + cfg.BlocksStorage.Bucket.StoragePrefix = "team-a/mimir_blocks" + cfg.AlertmanagerStorage.StoragePrefix = "team-a/mimir_alertmanager" + return cfg + }, + expectedError: nil, + }, { name: "GCS: should fail if bucket name is shared between alertmanager and blocks storage", getTestConfig: func() *Config { @@ -758,6 +792,29 @@ func TestConfigValidation(t *testing.T) { } } +func TestStoragePrefixesOverlap(t *testing.T) { + testCases := map[string]struct { + first string + second string + expected bool + }{ + "equal prefixes": {first: "team-a/mimir", second: "team-a/mimir", expected: true}, + "first prefix is parent": {first: "team-a", second: "team-a/mimir", expected: true}, + "second prefix is parent": { + first: "team-a/mimir", second: "team-a", expected: true, + }, + "sibling prefixes": {first: "team-a/blocks", second: "team-a/ruler", expected: false}, + "similar segment names": {first: "team-a", second: "team-alerts", expected: false}, + "empty and named prefix": {first: "", second: "team-a", expected: false}, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.expected, storagePrefixesOverlap(tc.first, tc.second)) + }) + } +} + // validCompartmentsConfig returns a config with a valid compartments setup (compartments and ingest // storage enabled, a read-compartment-templated topic, and two read and write compartments). func validCompartmentsConfig() *Config {