operator: Fix storing authentication credentials in the Loki ConfigMap (#11357)

Co-authored-by: Robert Jacob <xperimental@solidproject.de>
Co-authored-by: Robert Jacob <rojacob@redhat.com>
pull/9987/head^2
Periklis Tsirakidis 2 years ago committed by GitHub
parent 8dde7b9d49
commit c573defcbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      operator/CHANGELOG.md
  2. 10
      operator/cmd/loki-broker/main.go
  3. 2
      operator/docs/lokistack/object_storage.md
  4. 179
      operator/internal/handlers/internal/storage/secrets.go
  5. 90
      operator/internal/handlers/internal/storage/secrets_test.go
  6. 2
      operator/internal/manifests/compactor.go
  7. 35
      operator/internal/manifests/compactor_test.go
  8. 6
      operator/internal/manifests/config.go
  9. 2
      operator/internal/manifests/distributor.go
  10. 32
      operator/internal/manifests/distributor_test.go
  11. 2
      operator/internal/manifests/gateway.go
  12. 48
      operator/internal/manifests/gateway_test.go
  13. 2
      operator/internal/manifests/indexgateway.go
  14. 34
      operator/internal/manifests/indexgateway_test.go
  15. 2
      operator/internal/manifests/ingester.go
  16. 34
      operator/internal/manifests/ingester_test.go
  17. 204
      operator/internal/manifests/internal/config/build_test.go
  18. 18
      operator/internal/manifests/internal/config/loki-config.yaml
  19. 2
      operator/internal/manifests/querier.go
  20. 33
      operator/internal/manifests/querier_test.go
  21. 2
      operator/internal/manifests/query-frontend.go
  22. 33
      operator/internal/manifests/query-frontend_test.go
  23. 2
      operator/internal/manifests/ruler.go
  24. 34
      operator/internal/manifests/ruler_test.go
  25. 189
      operator/internal/manifests/storage/configure.go
  26. 979
      operator/internal/manifests/storage/configure_test.go
  27. 21
      operator/internal/manifests/storage/options.go
  28. 4
      operator/internal/manifests/storage/schema_test.go
  29. 99
      operator/internal/manifests/storage/var.go
  30. 15
      operator/internal/manifests/var.go

@ -1,5 +1,6 @@
## Main
- [11357](https://github.com/grafana/loki/pull/11357) **periklis**: Fix storing authentication credentials in the Loki ConfigMap
- [11393](https://github.com/grafana/loki/pull/11393) **periklis**: Add infra annotations for OpenShift based deployments
- [11094](https://github.com/grafana/loki/pull/11094) **periklis**: Add support for blocking queries per tenant
- [11288](https://github.com/grafana/loki/pull/11288) **periklis**: Fix custom CA for object-store in ruler component

@ -57,8 +57,6 @@ func (c *config) registerFlags(f *flag.FlagSet) {
f.StringVar(&c.objectStorage.S3.Endpoint, "object-storage.s3.endpoint", "", "The S3 endpoint location.")
f.StringVar(&c.objectStorage.S3.Buckets, "object-storage.s3.buckets", "", "A comma-separated list of S3 buckets.")
f.StringVar(&c.objectStorage.S3.Region, "object-storage.s3.region", "", "An S3 region.")
f.StringVar(&c.objectStorage.S3.AccessKeyID, "object-storage.s3.access-key-id", "", "The access key id for S3.")
f.StringVar(&c.objectStorage.S3.AccessKeySecret, "object-storage.s3.access-key-secret", "", "The access key secret for S3.")
// Input and output file/dir options
f.StringVar(&c.crFilepath, "custom-resource.path", "", "Path to a custom resource YAML file.")
f.StringVar(&c.writeToDir, "output.write-dir", "", "write each file to the specified directory.")
@ -88,14 +86,6 @@ func (c *config) validateFlags(log logr.Logger) {
log.Info("-object-storage.s3.buckets flag is required")
os.Exit(1)
}
if cfg.objectStorage.S3.AccessKeyID == "" {
log.Info("-object-storage.s3.access.key.id flag is required")
os.Exit(1)
}
if cfg.objectStorage.S3.AccessKeySecret == "" {
log.Info("-object-storage.s3.access.key.secret flag is required")
os.Exit(1)
}
// Validate feature flags
if cfg.featureFlags.LokiStackAlerts && !cfg.featureFlags.ServiceMonitors {
log.Info("-with-prometheus-alerts flag requires -with-service-monitors")

@ -51,7 +51,7 @@ _Note_: Upon setting up LokiStack for any object storage provider, you should co
--from-literal=sse_kms_encryption_context="<OPTIONAL_AWS_SSE_KMS_ENCRYPTION_CONTEXT_JSON>"
```
See also official docs on [AWS KMS Key ID](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id) and [AWS KMS Encryption Context](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context).
See also official docs on [AWS KMS Key ID](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id) and [AWS KMS Encryption Context](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context) (**Note:** Only content without newlines allowed, because it is exposed via environment variable to the containers).
or with `SSE-S3` encryption

@ -1,6 +1,10 @@
package storage
import (
"crypto/sha1"
"fmt"
"sort"
"github.com/ViaQ/logerr/v2/kverrors"
corev1 "k8s.io/api/core/v1"
@ -8,11 +12,18 @@ import (
"github.com/grafana/loki/operator/internal/manifests/storage"
)
var hashSeparator = []byte(",")
// ExtractSecret reads a k8s secret into a manifest object storage struct if valid.
func ExtractSecret(s *corev1.Secret, secretType lokiv1.ObjectStorageSecretType) (*storage.Options, error) {
var err error
hash, err := hashSecretData(s)
if err != nil {
return nil, kverrors.Wrap(err, "error calculating hash for secret", "type", secretType)
}
storageOpts := storage.Options{
SecretName: s.Name,
SecretSHA1: hash,
SharedStore: secretType,
}
@ -37,48 +48,75 @@ func ExtractSecret(s *corev1.Secret, secretType lokiv1.ObjectStorageSecretType)
return &storageOpts, nil
}
func hashSecretData(s *corev1.Secret) (string, error) {
keys := make([]string, 0, len(s.Data))
for k := range s.Data {
keys = append(keys, k)
}
sort.Strings(keys)
h := sha1.New()
for _, k := range keys {
if _, err := h.Write([]byte(k)); err != nil {
return "", err
}
if _, err := h.Write(hashSeparator); err != nil {
return "", err
}
if _, err := h.Write(s.Data[k]); err != nil {
return "", err
}
if _, err := h.Write(hashSeparator); err != nil {
return "", err
}
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
func extractAzureConfigSecret(s *corev1.Secret) (*storage.AzureStorageConfig, error) {
// Extract and validate mandatory fields
env := s.Data["environment"]
env := s.Data[storage.KeyAzureEnvironmentName]
if len(env) == 0 {
return nil, kverrors.New("missing secret field", "field", "environment")
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureEnvironmentName)
}
container := s.Data["container"]
container := s.Data[storage.KeyAzureStorageContainerName]
if len(container) == 0 {
return nil, kverrors.New("missing secret field", "field", "container")
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageContainerName)
}
name := s.Data["account_name"]
name := s.Data[storage.KeyAzureStorageAccountName]
if len(name) == 0 {
return nil, kverrors.New("missing secret field", "field", "account_name")
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountName)
}
key := s.Data["account_key"]
key := s.Data[storage.KeyAzureStorageAccountKey]
if len(key) == 0 {
return nil, kverrors.New("missing secret field", "field", "account_key")
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountKey)
}
// Extract and validate optional fields
endpointSuffix := s.Data["endpoint_suffix"]
endpointSuffix := s.Data[storage.KeyAzureStorageEndpointSuffix]
return &storage.AzureStorageConfig{
Env: string(env),
Container: string(container),
AccountName: string(name),
AccountKey: string(key),
EndpointSuffix: string(endpointSuffix),
}, nil
}
func extractGCSConfigSecret(s *corev1.Secret) (*storage.GCSStorageConfig, error) {
// Extract and validate mandatory fields
bucket := s.Data["bucketname"]
bucket := s.Data[storage.KeyGCPStorageBucketName]
if len(bucket) == 0 {
return nil, kverrors.New("missing secret field", "field", "bucketname")
return nil, kverrors.New("missing secret field", "field", storage.KeyGCPStorageBucketName)
}
// Check if google authentication credentials is provided
keyJSON := s.Data["key.json"]
keyJSON := s.Data[storage.KeyGCPServiceAccountKeyFilename]
if len(keyJSON) == 0 {
return nil, kverrors.New("missing google authentication credentials", "field", "key.json")
return nil, kverrors.New("missing google authentication credentials", "field", storage.KeyGCPServiceAccountKeyFilename)
}
return &storage.GCSStorageConfig{
@ -88,25 +126,25 @@ func extractGCSConfigSecret(s *corev1.Secret) (*storage.GCSStorageConfig, error)
func extractS3ConfigSecret(s *corev1.Secret) (*storage.S3StorageConfig, error) {
// Extract and validate mandatory fields
endpoint := s.Data["endpoint"]
endpoint := s.Data[storage.KeyAWSEndpoint]
if len(endpoint) == 0 {
return nil, kverrors.New("missing secret field", "field", "endpoint")
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSEndpoint)
}
buckets := s.Data["bucketnames"]
buckets := s.Data[storage.KeyAWSBucketNames]
if len(buckets) == 0 {
return nil, kverrors.New("missing secret field", "field", "bucketnames")
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSBucketNames)
}
id := s.Data["access_key_id"]
id := s.Data[storage.KeyAWSAccessKeyID]
if len(id) == 0 {
return nil, kverrors.New("missing secret field", "field", "access_key_id")
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeyID)
}
secret := s.Data["access_key_secret"]
secret := s.Data[storage.KeyAWSAccessKeySecret]
if len(secret) == 0 {
return nil, kverrors.New("missing secret field", "field", "access_key_secret")
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeySecret)
}
// Extract and validate optional fields
region := s.Data["region"]
region := s.Data[storage.KeyAWSRegion]
sseCfg, err := extractS3SSEConfig(s.Data)
if err != nil {
@ -114,12 +152,10 @@ func extractS3ConfigSecret(s *corev1.Secret) (*storage.S3StorageConfig, error) {
}
return &storage.S3StorageConfig{
Endpoint: string(endpoint),
Buckets: string(buckets),
AccessKeyID: string(id),
AccessKeySecret: string(secret),
Region: string(region),
SSE: sseCfg,
Endpoint: string(endpoint),
Buckets: string(buckets),
Region: string(region),
SSE: sseCfg,
}, nil
}
@ -129,12 +165,12 @@ func extractS3SSEConfig(d map[string][]byte) (storage.S3SSEConfig, error) {
kmsKeyId, kmsEncryptionCtx string
)
switch sseType = storage.S3SSEType(d["sse_type"]); sseType {
switch sseType = storage.S3SSEType(d[storage.KeyAWSSSEType]); sseType {
case storage.SSEKMSType:
kmsEncryptionCtx = string(d["sse_kms_encryption_context"])
kmsKeyId = string(d["sse_kms_key_id"])
kmsEncryptionCtx = string(d[storage.KeyAWSSseKmsEncryptionContext])
kmsKeyId = string(d[storage.KeyAWSSseKmsKeyID])
if kmsKeyId == "" {
return storage.S3SSEConfig{}, kverrors.New("missing secret field", "field", "sse_kms_key_id")
return storage.S3SSEConfig{}, kverrors.New("missing secret field", "field", storage.KeyAWSSseKmsKeyID)
}
case storage.SSES3Type:
@ -142,7 +178,7 @@ func extractS3SSEConfig(d map[string][]byte) (storage.S3SSEConfig, error) {
return storage.S3SSEConfig{}, nil
default:
return storage.S3SSEConfig{}, kverrors.New("unsupported secret field value (Supported: SSE-KMS, SSE-S3)", "field", "sse_type", "value", sseType)
return storage.S3SSEConfig{}, kverrors.New("unsupported secret field value (Supported: SSE-KMS, SSE-S3)", "field", storage.KeyAWSSSEType, "value", sseType)
}
return storage.S3SSEConfig{
@ -154,57 +190,55 @@ func extractS3SSEConfig(d map[string][]byte) (storage.S3SSEConfig, error) {
func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, error) {
// Extract and validate mandatory fields
url := s.Data["auth_url"]
url := s.Data[storage.KeySwiftAuthURL]
if len(url) == 0 {
return nil, kverrors.New("missing secret field", "field", "auth_url")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftAuthURL)
}
username := s.Data["username"]
username := s.Data[storage.KeySwiftUsername]
if len(username) == 0 {
return nil, kverrors.New("missing secret field", "field", "username")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUsername)
}
userDomainName := s.Data["user_domain_name"]
userDomainName := s.Data[storage.KeySwiftUserDomainName]
if len(userDomainName) == 0 {
return nil, kverrors.New("missing secret field", "field", "user_domain_name")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainName)
}
userDomainID := s.Data["user_domain_id"]
userDomainID := s.Data[storage.KeySwiftUserDomainID]
if len(userDomainID) == 0 {
return nil, kverrors.New("missing secret field", "field", "user_domain_id")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainID)
}
userID := s.Data["user_id"]
userID := s.Data[storage.KeySwiftUserID]
if len(userID) == 0 {
return nil, kverrors.New("missing secret field", "field", "user_id")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserID)
}
password := s.Data["password"]
password := s.Data[storage.KeySwiftPassword]
if len(password) == 0 {
return nil, kverrors.New("missing secret field", "field", "password")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftPassword)
}
domainID := s.Data["domain_id"]
domainID := s.Data[storage.KeySwiftDomainID]
if len(domainID) == 0 {
return nil, kverrors.New("missing secret field", "field", "domain_id")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftDomainID)
}
domainName := s.Data["domain_name"]
domainName := s.Data[storage.KeySwiftDomainName]
if len(domainName) == 0 {
return nil, kverrors.New("missing secret field", "field", "domain_name")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftDomainName)
}
containerName := s.Data["container_name"]
containerName := s.Data[storage.KeySwiftContainerName]
if len(containerName) == 0 {
return nil, kverrors.New("missing secret field", "field", "container_name")
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftContainerName)
}
// Extract and validate optional fields
projectID := s.Data["project_id"]
projectName := s.Data["project_name"]
projectDomainID := s.Data["project_domain_id"]
projectDomainName := s.Data["project_domain_name"]
region := s.Data["region"]
projectID := s.Data[storage.KeySwiftProjectID]
projectName := s.Data[storage.KeySwiftProjectName]
projectDomainID := s.Data[storage.KeySwiftProjectDomainId]
projectDomainName := s.Data[storage.KeySwiftProjectDomainName]
region := s.Data[storage.KeySwiftRegion]
return &storage.SwiftStorageConfig{
AuthURL: string(url),
Username: string(username),
UserDomainName: string(userDomainName),
UserDomainID: string(userDomainID),
UserID: string(userID),
Password: string(password),
DomainID: string(domainID),
DomainName: string(domainName),
ProjectID: string(projectID),
@ -218,28 +252,25 @@ func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, er
func extractAlibabaCloudConfigSecret(s *corev1.Secret) (*storage.AlibabaCloudStorageConfig, error) {
// Extract and validate mandatory fields
endpoint := s.Data["endpoint"]
endpoint := s.Data[storage.KeyAlibabaCloudEndpoint]
if len(endpoint) == 0 {
return nil, kverrors.New("missing secret field", "field", "endpoint")
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudEndpoint)
}
bucket := s.Data["bucket"]
bucket := s.Data[storage.KeyAlibabaCloudBucket]
if len(bucket) == 0 {
return nil, kverrors.New("missing secret field", "field", "bucket")
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudBucket)
}
// TODO buckets are comma-separated list
id := s.Data["access_key_id"]
id := s.Data[storage.KeyAlibabaCloudAccessKeyID]
if len(id) == 0 {
return nil, kverrors.New("missing secret field", "field", "access_key_id")
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudAccessKeyID)
}
secret := s.Data["secret_access_key"]
secret := s.Data[storage.KeyAlibabaCloudSecretAccessKey]
if len(secret) == 0 {
return nil, kverrors.New("missing secret field", "field", "secret_access_key")
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudSecretAccessKey)
}
return &storage.AlibabaCloudStorageConfig{
Endpoint: string(endpoint),
Bucket: string(bucket),
AccessKeyID: string(id),
SecretAccessKey: string(secret),
Endpoint: string(endpoint),
Bucket: string(bucket),
}, nil
}

@ -1,14 +1,66 @@
package storage_test
package storage
import (
"testing"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/handlers/internal/storage"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
)
func TestHashSecretData(t *testing.T) {
tt := []struct {
desc string
data map[string][]byte
wantHash string
}{
{
desc: "nil",
data: nil,
wantHash: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
},
{
desc: "empty",
data: map[string][]byte{},
wantHash: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
},
{
desc: "single entry",
data: map[string][]byte{
"key": []byte("value"),
},
wantHash: "a8973b2094d3af1e43931132dee228909bf2b02a",
},
{
desc: "multiple entries",
data: map[string][]byte{
"key": []byte("value"),
"key3": []byte("value3"),
"key2": []byte("value2"),
},
wantHash: "a3341093891ad4df9f07db586029be48e9e6e884",
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
s := &corev1.Secret{
Data: tc.data,
}
hash, err := hashSecretData(s)
require.NoError(t, err)
require.Equal(t, tc.wantHash, hash)
})
}
}
func TestAzureExtract(t *testing.T) {
type test struct {
name string
@ -43,6 +95,7 @@ func TestAzureExtract(t *testing.T) {
{
name: "missing account_key",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"container": []byte("this,that"),
@ -54,6 +107,7 @@ func TestAzureExtract(t *testing.T) {
{
name: "all mandatory set",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"container": []byte("this,that"),
@ -65,6 +119,7 @@ func TestAzureExtract(t *testing.T) {
{
name: "all set including optional",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"container": []byte("this,that"),
@ -80,9 +135,12 @@ func TestAzureExtract(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
_, err := storage.ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretAzure)
opts, err := ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretAzure)
if !tst.wantErr {
require.NoError(t, err)
require.NotEmpty(t, opts.SecretName)
require.NotEmpty(t, opts.SecretSHA1)
require.Equal(t, opts.SharedStore, lokiv1.ObjectStorageSecretAzure)
}
if tst.wantErr {
require.NotNil(t, err)
@ -115,6 +173,7 @@ func TestGCSExtract(t *testing.T) {
{
name: "all set",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"bucketname": []byte("here"),
"key.json": []byte("{\"type\": \"SA\"}"),
@ -127,7 +186,7 @@ func TestGCSExtract(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
_, err := storage.ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretGCS)
_, err := ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretGCS)
if !tst.wantErr {
require.NoError(t, err)
}
@ -210,6 +269,7 @@ func TestS3Extract(t *testing.T) {
{
name: "all set with SSE-KMS",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"endpoint": []byte("here"),
"bucketnames": []byte("this,that"),
@ -223,6 +283,7 @@ func TestS3Extract(t *testing.T) {
{
name: "all set with SSE-KMS with encryption context",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"endpoint": []byte("here"),
"bucketnames": []byte("this,that"),
@ -237,6 +298,7 @@ func TestS3Extract(t *testing.T) {
{
name: "all set with SSE-S3",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"endpoint": []byte("here"),
"bucketnames": []byte("this,that"),
@ -249,6 +311,7 @@ func TestS3Extract(t *testing.T) {
{
name: "all set without SSE",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"endpoint": []byte("here"),
"bucketnames": []byte("this,that"),
@ -263,9 +326,12 @@ func TestS3Extract(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
_, err := storage.ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretS3)
opts, err := ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretS3)
if !tst.wantErr {
require.NoError(t, err)
require.NotEmpty(t, opts.SecretName)
require.NotEmpty(t, opts.SecretSHA1)
require.Equal(t, opts.SharedStore, lokiv1.ObjectStorageSecretS3)
}
if tst.wantErr {
require.NotNil(t, err)
@ -389,6 +455,7 @@ func TestSwiftExtract(t *testing.T) {
{
name: "all set",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"auth_url": []byte("here"),
"username": []byte("this,that"),
@ -408,9 +475,12 @@ func TestSwiftExtract(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
_, err := storage.ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretSwift)
opts, err := ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretSwift)
if !tst.wantErr {
require.NoError(t, err)
require.NotEmpty(t, opts.SecretName)
require.NotEmpty(t, opts.SecretSHA1)
require.Equal(t, opts.SharedStore, lokiv1.ObjectStorageSecretSwift)
}
if tst.wantErr {
require.NotNil(t, err)
@ -464,6 +534,7 @@ func TestAlibabaCloudExtract(t *testing.T) {
{
name: "all set",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"endpoint": []byte("here"),
"bucket": []byte("this,that"),
@ -478,9 +549,12 @@ func TestAlibabaCloudExtract(t *testing.T) {
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
_, err := storage.ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretAlibabaCloud)
opts, err := ExtractSecret(tst.secret, lokiv1.ObjectStorageSecretAlibabaCloud)
if !tst.wantErr {
require.NoError(t, err)
require.NotEmpty(t, opts.SecretName)
require.NotEmpty(t, opts.SecretSHA1)
require.Equal(t, opts.SharedStore, lokiv1.ObjectStorageSecretAlibabaCloud)
}
if tst.wantErr {
require.NotNil(t, err)

@ -67,7 +67,7 @@ func BuildCompactor(opts Options) ([]client.Object, error) {
// NewCompactorStatefulSet creates a statefulset object for a compactor.
func NewCompactorStatefulSet(opts Options) *appsv1.StatefulSet {
l := ComponentLabels(LabelCompactorComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelCompactorComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Compactor),
Volumes: []corev1.Volume{

@ -4,6 +4,7 @@ import (
"testing"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
"github.com/stretchr/testify/require"
)
@ -48,10 +49,32 @@ func TestNewCompactorStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
},
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewCompactorStatefulSet_HasTemplateObjectStorageHashAnnotation(t *testing.T) {
ss := NewCompactorStatefulSet(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
StorageClassName: "standard",
Template: &lokiv1.LokiTemplateSpec{
Compactor: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewCompactorStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -68,8 +91,8 @@ func TestNewCompactorStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *
},
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}

@ -47,9 +47,9 @@ func LokiConfigMap(opt Options) (*corev1.ConfigMap, string, error) {
Name: lokiConfigMapName(opt.Name),
Labels: commonLabels(opt.Name),
},
BinaryData: map[string][]byte{
config.LokiConfigFileName: c,
config.LokiRuntimeConfigFileName: rc,
Data: map[string]string{
config.LokiConfigFileName: string(c),
config.LokiRuntimeConfigFileName: string(rc),
},
}, sha1C, nil
}

@ -67,7 +67,7 @@ func BuildDistributor(opts Options) ([]client.Object, error) {
// NewDistributorDeployment creates a deployment object for a distributor
func NewDistributorDeployment(opts Options) *appsv1.Deployment {
l := ComponentLabels(LabelDistributorComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelDistributorComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Distributor),
Volumes: []corev1.Volume{

@ -11,6 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewDistributorDeployment_SelectorMatchesLabels(t *testing.T) {
@ -47,10 +48,30 @@ func TestNewDistributorDeployment_HasTemplateConfigHashAnnotation(t *testing.T)
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewDistributorDeployment_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewDistributorDeployment(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
Template: &lokiv1.LokiTemplateSpec{
Distributor: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewDistributorDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -67,10 +88,9 @@ func TestNewDistributorDeployment_HasTemplateCertRotationRequiredAtAnnotation(t
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestBuildDistributor_PodDisruptionBudget(t *testing.T) {

@ -114,7 +114,7 @@ func BuildGateway(opts Options) ([]client.Object, error) {
// NewGatewayDeployment creates a deployment object for a lokiStack-gateway
func NewGatewayDeployment(opts Options, sha1C string) *appsv1.Deployment {
l := ComponentLabels(LabelGatewayComponent, opts.Name)
a := commonAnnotations(sha1C, opts.CertRotationRequiredAt)
a := commonAnnotations(sha1C, "", opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
ServiceAccountName: GatewayName(opts.Name),
Affinity: configureAffinity(LabelGatewayComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Gateway),

@ -10,6 +10,7 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/internal/gateway"
"github.com/grafana/loki/operator/internal/manifests/openshift"
"github.com/grafana/loki/operator/internal/manifests/storage"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
@ -51,10 +52,46 @@ func TestNewGatewayDeployment_HasTemplateConfigHashAnnotation(t *testing.T) {
Timeouts: defaultTimeoutConfig,
}, sha1C)
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], sha1C)
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], sha1C)
}
func TestNewGatewayDeployment_HasNotTemplateObjectStoreHashAnnotation(t *testing.T) {
sha1C := "deadbeef"
ss := NewGatewayDeployment(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
Template: &lokiv1.LokiTemplateSpec{
Compactor: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
Distributor: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
Gateway: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
Ingester: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
Querier: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
QueryFrontend: &lokiv1.LokiComponentSpec{
Replicas: rand.Int31(),
},
},
},
Timeouts: defaultTimeoutConfig,
}, sha1C)
annotations := ss.Spec.Template.Annotations
require.NotContains(t, annotations, AnnotationLokiObjectStoreHash)
}
func TestNewGatewayDeployment_HasNodeSelector(t *testing.T) {
@ -134,10 +171,9 @@ func TestNewGatewayDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *tes
Timeouts: defaultTimeoutConfig,
}, sha1C)
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestGatewayConfigMap_ReturnsSHA1OfBinaryContents(t *testing.T) {

@ -73,7 +73,7 @@ func BuildIndexGateway(opts Options) ([]client.Object, error) {
// NewIndexGatewayStatefulSet creates a statefulset object for an index-gateway
func NewIndexGatewayStatefulSet(opts Options) *appsv1.StatefulSet {
l := ComponentLabels(LabelIndexGatewayComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelIndexGatewayComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.IndexGateway),
Volumes: []corev1.Volume{

@ -10,6 +10,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewIndexGatewayStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
@ -27,10 +28,31 @@ func TestNewIndexGatewayStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewIndexGatewayStatefulSet_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewIndexGatewayStatefulSet(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
StorageClassName: "standard",
Template: &lokiv1.LokiTemplateSpec{
IndexGateway: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewIndexGatewayStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -47,10 +69,10 @@ func TestNewIndexGatewayStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(
},
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestNewIndexGatewayStatefulSet_SelectorMatchesLabels(t *testing.T) {

@ -73,7 +73,7 @@ func BuildIngester(opts Options) ([]client.Object, error) {
// NewIngesterStatefulSet creates a deployment object for an ingester
func NewIngesterStatefulSet(opts Options) *appsv1.StatefulSet {
l := ComponentLabels(LabelIngesterComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelIngesterComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Ingester),
Volumes: []corev1.Volume{

@ -13,6 +13,7 @@ import (
v1 "github.com/grafana/loki/operator/apis/config/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/internal"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewIngesterStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
@ -30,10 +31,31 @@ func TestNewIngesterStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewIngesterStatefulSet_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewIngesterStatefulSet(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
StorageClassName: "standard",
Template: &lokiv1.LokiTemplateSpec{
Ingester: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewIngesterStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -50,10 +72,10 @@ func TestNewIngesterStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *t
},
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestNewIngesterStatefulSet_SelectorMatchesLabels(t *testing.T) {

@ -28,8 +28,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -245,11 +245,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -287,8 +285,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -580,11 +578,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -666,11 +662,9 @@ func TestBuild_ConfigAndRuntimeConfig_CreateLokiConfigFailed(t *testing.T) {
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -702,8 +696,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -1020,11 +1014,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -1062,8 +1054,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -1381,11 +1373,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -1423,8 +1413,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -1772,11 +1762,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -1814,8 +1802,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -2109,11 +2097,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -2154,8 +2140,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -2533,11 +2519,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -2575,8 +2559,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -2881,11 +2865,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -2923,8 +2905,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -3379,11 +3361,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -3421,8 +3401,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -3641,11 +3621,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -3683,8 +3661,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -3905,11 +3883,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -3947,8 +3923,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -4167,11 +4143,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -4209,13 +4183,13 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
sse:
type: SSE-KMS
kms_key_id: test
kms_encryption_context: |
{"key": "value", "another":"value1"}
${AWS_SSE_KMS_ENCRYPTION_CONTEXT}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -4466,11 +4440,10 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
SSE: storage.S3SSEConfig{
Type: storage.SSEKMSType,
KMSKeyID: "test",
@ -4512,8 +4485,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
sse:
type: SSE-S3
s3forcepathstyle: true
@ -4766,11 +4739,10 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
SSE: storage.S3SSEConfig{
Type: storage.SSES3Type,
KMSKeyID: "test",
@ -4812,8 +4784,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:
@ -5025,11 +4997,9 @@ overrides:
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -5116,11 +5086,9 @@ func defaultOptions() Options {
ObjectStorage: storage.Options{
SharedStore: lokiv1.ObjectStorageSecretS3,
S3: &storage.S3StorageConfig{
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
AccessKeyID: "test",
AccessKeySecret: "test123",
Endpoint: "http://test.default.svc.cluster.local.:9000",
Region: "us-east",
Buckets: "loki",
},
Schemas: []lokiv1.ObjectStorageSchema{
{
@ -5305,8 +5273,8 @@ common:
s3: http://test.default.svc.cluster.local.:9000
bucketnames: loki
region: us-east
access_key_id: test
secret_access_key: test123
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
s3forcepathstyle: true
compactor_grpc_address: loki-compactor-grpc-lokistack-dev.default.svc.cluster.local:9095
ring:

@ -12,8 +12,8 @@ common:
azure:
environment: {{ .Env }}
container_name: {{ .Container }}
account_name: {{ .AccountName }}
account_key: {{ .AccountKey }}
account_name: ${AZURE_STORAGE_ACCOUNT_NAME}
account_key: ${AZURE_STORAGE_ACCOUNT_KEY}
{{- with .EndpointSuffix }}
endpoint_suffix: {{ . }}
{{- end }}
@ -27,8 +27,8 @@ common:
s3: {{ .Endpoint }}
bucketnames: {{ .Buckets }}
region: {{ .Region }}
access_key_id: {{ .AccessKeyID }}
secret_access_key: {{ .AccessKeySecret }}
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_ACCESS_KEY_SECRET}
{{- with .SSE }}
{{- if .Type }}
sse:
@ -37,7 +37,7 @@ common:
kms_key_id: {{ .KMSKeyID }}
{{- with .KMSEncryptionContext }}
kms_encryption_context: |
{{ . }}
${AWS_SSE_KMS_ENCRYPTION_CONTEXT}
{{- end }}
{{- end}}
{{- end }}
@ -47,11 +47,11 @@ common:
{{- with .ObjectStorage.Swift }}
swift:
auth_url: {{ .AuthURL }}
username: {{ .Username }}
username: ${SWIFT_USERNAME}
user_domain_name: {{ .UserDomainName }}
user_domain_id: {{ .UserDomainID }}
user_id: {{ .UserID }}
password: {{ .Password }}
password: ${SWIFT_PASSWORD}
domain_id: {{ .DomainID }}
domain_name: {{ .DomainName }}
project_id: {{ .ProjectID }}
@ -65,8 +65,8 @@ common:
alibabacloud:
bucket: {{ .Bucket }}
endpoint: {{ .Endpoint }}
access_key_id: {{ .AccessKeyID }}
secret_access_key: {{ .SecretAccessKey }}
access_key_id: ${ALIBABA_CLOUD_ACCESS_KEY_ID}
secret_access_key: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET}
{{- end }}
compactor_grpc_address: {{ .Compactor.FQDN }}:{{ .Compactor.Port }}
{{- with .GossipRing }}

@ -73,7 +73,7 @@ func BuildQuerier(opts Options) ([]client.Object, error) {
// NewQuerierDeployment creates a deployment object for a querier
func NewQuerierDeployment(opts Options) *appsv1.Deployment {
l := ComponentLabels(LabelQuerierComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelQuerierComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Querier),
Volumes: []corev1.Volume{

@ -11,6 +11,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewQuerierDeployment_HasTemplateConfigHashAnnotation(t *testing.T) {
@ -28,10 +29,31 @@ func TestNewQuerierDeployment_HasTemplateConfigHashAnnotation(t *testing.T) {
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewQuerierDeployment_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewQuerierDeployment(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
StorageClassName: "standard",
Template: &lokiv1.LokiTemplateSpec{
Querier: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewQuerierDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -48,10 +70,9 @@ func TestNewQuerierDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *tes
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestNewQuerierDeployment_SelectorMatchesLabels(t *testing.T) {

@ -67,7 +67,7 @@ func BuildQueryFrontend(opts Options) ([]client.Object, error) {
// NewQueryFrontendDeployment creates a deployment object for a query-frontend
func NewQueryFrontendDeployment(opts Options) *appsv1.Deployment {
l := ComponentLabels(LabelQueryFrontendComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelQueryFrontendComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.QueryFrontend),
Volumes: []corev1.Volume{

@ -10,6 +10,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewQueryFrontendDeployment_SelectorMatchesLabels(t *testing.T) {
@ -44,10 +45,31 @@ func TestNewQueryFrontendDeployment_HasTemplateConfigHashAnnotation(t *testing.T
},
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewQueryFrontendDeployment_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewQueryFrontendDeployment(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
Template: &lokiv1.LokiTemplateSpec{
QueryFrontend: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewQueryFrontendDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -64,10 +86,9 @@ func TestNewQueryFrontendDeployment_HasTemplateCertRotationRequiredAtAnnotation(
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestBuildQueryFrontend_PodDisruptionBudget(t *testing.T) {

@ -97,7 +97,7 @@ func NewRulerStatefulSet(opts Options) *appsv1.StatefulSet {
}
l := ComponentLabels(LabelRulerComponent, opts.Name)
a := commonAnnotations(opts.ConfigSHA1, opts.CertRotationRequiredAt)
a := commonAnnotations(opts.ConfigSHA1, opts.ObjectStorage.SecretSHA1, opts.CertRotationRequiredAt)
podSpec := corev1.PodSpec{
Affinity: configureAffinity(LabelRulerComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.Ruler),
Volumes: []corev1.Volume{

@ -12,6 +12,7 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/openshift"
"github.com/grafana/loki/operator/internal/manifests/storage"
)
func TestNewRulerStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
@ -29,10 +30,31 @@ func TestNewRulerStatefulSet_HasTemplateConfigHashAnnotation(t *testing.T) {
},
})
expected := "loki.grafana.com/config-hash"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationLokiConfigHash)
require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef")
}
func TestNewRulerStatefulSet_HasTemplateObjectStoreHashAnnotation(t *testing.T) {
ss := NewRulerStatefulSet(Options{
Name: "abcd",
Namespace: "efgh",
ObjectStorage: storage.Options{
SecretSHA1: "deadbeef",
},
Stack: lokiv1.LokiStackSpec{
StorageClassName: "standard",
Template: &lokiv1.LokiTemplateSpec{
Ruler: &lokiv1.LokiComponentSpec{
Replicas: 1,
},
},
},
})
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, AnnotationLokiObjectStoreHash)
require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef")
}
func TestNewRulerStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) {
@ -49,10 +71,10 @@ func TestNewRulerStatefulSet_HasTemplateCertRotationRequiredAtAnnotation(t *test
},
},
})
expected := "loki.grafana.com/certRotationRequiredAt"
annotations := ss.Spec.Template.Annotations
require.Contains(t, annotations, expected)
require.Equal(t, annotations[expected], "deadbeef")
require.Contains(t, annotations, AnnotationCertRotationRequiredAt)
require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef")
}
func TestBuildRuler_HasExtraObjectsForTenantMode(t *testing.T) {

@ -12,28 +12,19 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
)
const (
// EnvGoogleApplicationCredentials is the environment variable to specify path to key.json
EnvGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS"
// GCSFileName is the file containing the Google credentials for authentication
GCSFileName = "key.json"
secretDirectory = "/etc/storage/secrets"
storageTLSVolume = "storage-tls"
caDirectory = "/etc/storage/ca"
)
// ConfigureDeployment appends additional pod volumes and container env vars, args, volume mounts
// based on the object storage type. Currently supported amendments:
// - All: Ensure object storage secret mounted and auth projected as env vars.
// - GCS: Ensure env var GOOGLE_APPLICATION_CREDENTIALS in container
// - S3: Ensure mounting custom CA configmap if any TLSConfig given
func ConfigureDeployment(d *appsv1.Deployment, opts Options) error {
switch opts.SharedStore {
case lokiv1.ObjectStorageSecretGCS:
return configureDeployment(d, opts.SecretName)
case lokiv1.ObjectStorageSecretAlibabaCloud, lokiv1.ObjectStorageSecretAzure, lokiv1.ObjectStorageSecretGCS, lokiv1.ObjectStorageSecretSwift:
return configureDeployment(d, opts)
case lokiv1.ObjectStorageSecretS3:
if opts.TLS == nil {
return nil
err := configureDeployment(d, opts)
if err != nil {
return err
}
return configureDeploymentCA(d, opts.TLS)
default:
@ -43,15 +34,16 @@ func ConfigureDeployment(d *appsv1.Deployment, opts Options) error {
// ConfigureStatefulSet appends additional pod volumes and container env vars, args, volume mounts
// based on the object storage type. Currently supported amendments:
// - All: Ensure object storage secret mounted and auth projected as env vars.
// - GCS: Ensure env var GOOGLE_APPLICATION_CREDENTIALS in container
// - S3: Ensure mounting custom CA configmap if any TLSConfig given
func ConfigureStatefulSet(d *appsv1.StatefulSet, opts Options) error {
switch opts.SharedStore {
case lokiv1.ObjectStorageSecretGCS:
return configureStatefulSet(d, opts.SecretName)
case lokiv1.ObjectStorageSecretAlibabaCloud, lokiv1.ObjectStorageSecretAzure, lokiv1.ObjectStorageSecretGCS, lokiv1.ObjectStorageSecretSwift:
return configureStatefulSet(d, opts)
case lokiv1.ObjectStorageSecretS3:
if opts.TLS == nil {
return nil
if err := configureStatefulSet(d, opts); err != nil {
return err
}
return configureStatefulSetCA(d, opts.TLS)
default:
@ -59,10 +51,10 @@ func ConfigureStatefulSet(d *appsv1.StatefulSet, opts Options) error {
}
}
// ConfigureDeployment merges a GCS Object Storage volume into the deployment spec.
// With this, the deployment will expose an environment variable for Google authentication.
func configureDeployment(d *appsv1.Deployment, secretName string) error {
p := ensureCredentialsForGCS(&d.Spec.Template.Spec, secretName)
// ConfigureDeployment merges the object storage secret volume into the deployment spec.
// With this, the deployment will expose credentials specific environment variables.
func configureDeployment(d *appsv1.Deployment, opts Options) error {
p := ensureObjectStoreCredentials(&d.Spec.Template.Spec, opts)
if err := mergo.Merge(&d.Spec.Template.Spec, p, mergo.WithOverride); err != nil {
return kverrors.Wrap(err, "failed to merge gcs object storage spec ")
@ -73,6 +65,10 @@ func configureDeployment(d *appsv1.Deployment, secretName string) error {
// ConfigureDeploymentCA merges a S3 CA ConfigMap volume into the deployment spec.
func configureDeploymentCA(d *appsv1.Deployment, tls *TLSConfig) error {
if tls == nil {
return nil
}
p := ensureCAForS3(&d.Spec.Template.Spec, tls)
if err := mergo.Merge(&d.Spec.Template.Spec, p, mergo.WithOverride); err != nil {
@ -82,10 +78,10 @@ func configureDeploymentCA(d *appsv1.Deployment, tls *TLSConfig) error {
return nil
}
// ConfigureStatefulSet merges a GCS Object Storage volume into the statefulset spec.
// With this, the statefulset will expose an environment variable for Google authentication.
func configureStatefulSet(s *appsv1.StatefulSet, secretName string) error {
p := ensureCredentialsForGCS(&s.Spec.Template.Spec, secretName)
// ConfigureStatefulSet merges a the object storage secrect volume into the statefulset spec.
// With this, the statefulset will expose credentials specific environment variable.
func configureStatefulSet(s *appsv1.StatefulSet, opts Options) error {
p := ensureObjectStoreCredentials(&s.Spec.Template.Spec, opts)
if err := mergo.Merge(&s.Spec.Template.Spec, p, mergo.WithOverride); err != nil {
return kverrors.Wrap(err, "failed to merge gcs object storage spec ")
@ -96,6 +92,10 @@ func configureStatefulSet(s *appsv1.StatefulSet, secretName string) error {
// ConfigureStatefulSetCA merges a S3 CA ConfigMap volume into the statefulset spec.
func configureStatefulSetCA(s *appsv1.StatefulSet, tls *TLSConfig) error {
if tls == nil {
return nil
}
p := ensureCAForS3(&s.Spec.Template.Spec, tls)
if err := mergo.Merge(&s.Spec.Template.Spec, p, mergo.WithOverride); err != nil {
@ -105,9 +105,11 @@ func configureStatefulSetCA(s *appsv1.StatefulSet, tls *TLSConfig) error {
return nil
}
func ensureCredentialsForGCS(p *corev1.PodSpec, secretName string) corev1.PodSpec {
func ensureObjectStoreCredentials(p *corev1.PodSpec, opts Options) corev1.PodSpec {
container := p.Containers[0].DeepCopy()
volumes := p.Volumes
secretName := opts.SecretName
storeType := opts.SharedStore
volumes = append(volumes, corev1.Volume{
Name: secretName,
@ -124,10 +126,133 @@ func ensureCredentialsForGCS(p *corev1.PodSpec, secretName string) corev1.PodSpe
MountPath: secretDirectory,
})
container.Env = append(container.Env, corev1.EnvVar{
Name: EnvGoogleApplicationCredentials,
Value: path.Join(secretDirectory, GCSFileName),
})
var storeEnvVars []corev1.EnvVar
switch storeType {
case lokiv1.ObjectStorageSecretAlibabaCloud:
storeEnvVars = []corev1.EnvVar{
{
Name: EnvAlibabaCloudAccessKeyID,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAlibabaCloudAccessKeyID,
},
},
},
{
Name: EnvAlibabaCloudAccessKeySecret,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAlibabaCloudSecretAccessKey,
},
},
},
}
case lokiv1.ObjectStorageSecretAzure:
storeEnvVars = []corev1.EnvVar{
{
Name: EnvAzureStorageAccountName,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAzureStorageAccountName,
},
},
},
{
Name: EnvAzureStorageAccountKey,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAzureStorageAccountKey,
},
},
},
}
case lokiv1.ObjectStorageSecretGCS:
storeEnvVars = []corev1.EnvVar{
{
Name: EnvGoogleApplicationCredentials,
Value: path.Join(secretDirectory, KeyGCPServiceAccountKeyFilename),
},
}
case lokiv1.ObjectStorageSecretS3:
storeEnvVars = []corev1.EnvVar{
{
Name: EnvAWSAccessKeyID,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAWSAccessKeyID,
},
},
},
{
Name: EnvAWSAccessKeySecret,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAWSAccessKeySecret,
},
},
},
}
if opts.S3 != nil && opts.S3.SSE.Type == SSEKMSType && opts.S3.SSE.KMSEncryptionContext != "" {
storeEnvVars = append(storeEnvVars, corev1.EnvVar{
Name: EnvAWSSseKmsEncryptionContext,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeyAWSSseKmsEncryptionContext,
},
},
})
}
case lokiv1.ObjectStorageSecretSwift:
storeEnvVars = []corev1.EnvVar{
{
Name: EnvSwiftUsername,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeySwiftUsername,
},
},
},
{
Name: EnvSwiftPassword,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secretName,
},
Key: KeySwiftPassword,
},
},
},
}
}
container.Env = append(container.Env, storeEnvVars...)
return corev1.PodSpec{
Containers: []corev1.Container{

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@ type Options struct {
AlibabaCloud *AlibabaCloudStorageConfig
SecretName string
SecretSHA1 string
TLS *TLSConfig
}
@ -24,8 +25,6 @@ type Options struct {
type AzureStorageConfig struct {
Env string
Container string
AccountName string
AccountKey string
EndpointSuffix string
}
@ -36,12 +35,10 @@ type GCSStorageConfig struct {
// S3StorageConfig for S3 storage config
type S3StorageConfig struct {
Endpoint string
Region string
Buckets string
AccessKeyID string
AccessKeySecret string
SSE S3SSEConfig
Endpoint string
Region string
Buckets string
SSE S3SSEConfig
}
type S3SSEType string
@ -60,11 +57,9 @@ type S3SSEConfig struct {
// SwiftStorageConfig for Swift storage config
type SwiftStorageConfig struct {
AuthURL string
Username string
UserDomainName string
UserDomainID string
UserID string
Password string
DomainID string
DomainName string
ProjectID string
@ -77,10 +72,8 @@ type SwiftStorageConfig struct {
// AlibabaCloudStorageConfig for AlibabaCloud storage config
type AlibabaCloudStorageConfig struct {
Endpoint string
Bucket string
AccessKeyID string
SecretAccessKey string
Endpoint string
Bucket string
}
// TLSConfig for object storage endpoints. Currently supported only by:

@ -4,9 +4,9 @@ import (
"testing"
"time"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/stretchr/testify/require"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
)
func TestBuildSchemaConfig_NoSchemas(t *testing.T) {

@ -0,0 +1,99 @@
package storage
const (
// EnvAlibabaCloudAccessKeyID is the environment variable to specify the AlibabaCloud client id to access S3.
EnvAlibabaCloudAccessKeyID = "ALIBABA_CLOUD_ACCESS_KEY_ID"
// EnvAlibabaCloudAccessKeySecret is the environment variable to specify the AlibabaCloud client secret to access S3.
EnvAlibabaCloudAccessKeySecret = "ALIBABA_CLOUD_ACCESS_KEY_SECRET"
// EnvAWSAccessKeyID is the environment variable to specify the AWS client id to access S3.
EnvAWSAccessKeyID = "AWS_ACCESS_KEY_ID"
// EnvAWSAccessKeySecret is the environment variable to specify the AWS client secret to access S3.
EnvAWSAccessKeySecret = "AWS_ACCESS_KEY_SECRET"
// EnvAWSSseKmsEncryptionContext is the environment variable to specity the AWS KMS encryption context when using type SSE-KMS.
EnvAWSSseKmsEncryptionContext = "AWS_SSE_KMS_ENCRYPTION_CONTEXT"
// EnvAzureStorageAccountName is the environment variable to specify the Azure storage account name to access the container.
EnvAzureStorageAccountName = "AZURE_STORAGE_ACCOUNT_NAME"
// EnvAzureStorageAccountKey is the environment variable to specify the Azure storage account key to access the container.
EnvAzureStorageAccountKey = "AZURE_STORAGE_ACCOUNT_KEY"
// EnvGoogleApplicationCredentials is the environment variable to specify path to key.json
EnvGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS"
// EnvSwiftPassword is the environment variable to specify the OpenStack Swift password.
EnvSwiftPassword = "SWIFT_PASSWORD"
// EnvSwiftUsername is the environment variable to specify the OpenStack Swift username.
EnvSwiftUsername = "SWIFT_USERNAME"
// KeyAlibabaCloudAccessKeyID is the secret data key for the AlibabaCloud client id to access S3.
KeyAlibabaCloudAccessKeyID = "access_key_id"
// KeyAlibabaCloudSecretAccessKey is the secret data key for the AlibabaCloud client secret to access S3.
KeyAlibabaCloudSecretAccessKey = "secret_access_key"
// KeyAlibabaCloudBucket is the secret data key for the S3 bucket name.
KeyAlibabaCloudBucket = "bucket"
// KeyAlibabaCloudEndpoint is the secret data key for the S3 endpoint URL.
KeyAlibabaCloudEndpoint = "endpoint"
// KeyAWSAccessKeyID is the secret data key for the AWS client id to access S3.
KeyAWSAccessKeyID = "access_key_id"
// KeyAWSAccessKeySecret is the secret data key for the AWS client secret to access S3.
KeyAWSAccessKeySecret = "access_key_secret"
// KeyAWSBucketNames is the secret data key for the AWS S3 bucket names.
KeyAWSBucketNames = "bucketnames"
// KeyAWSEndpoint is the secret data key for the AWS endpoint URL.
KeyAWSEndpoint = "endpoint"
// KeyAWSRegion is the secret data key for the AWS region.
KeyAWSRegion = "region"
// KeyAWSSSEType is the secret data key for the AWS server-side encryption type.
KeyAWSSSEType = "sse_type"
// KeyAWSSseKmsEncryptionContext is the secret data key for the AWS SSE KMS encryption context.
KeyAWSSseKmsEncryptionContext = "sse_kms_encryption_context"
// KeyAWSSseKmsKeyID is the secret data key for the AWS SSE KMS key id.
KeyAWSSseKmsKeyID = "sse_kms_key_id"
// KeyAzureStorageAccountKey is the secret data key for the Azure storage account key.
KeyAzureStorageAccountKey = "account_key"
// KeyAzureStorageAccountName is the secret data key for the Azure storage account name.
KeyAzureStorageAccountName = "account_name"
// KeyAzureStorageContainerName is the secret data key for the Azure storage container name.
KeyAzureStorageContainerName = "container"
// KeyAzureStorageEndpointSuffix is the secret data key for the Azure storage endpoint URL suffix.
KeyAzureStorageEndpointSuffix = "endpoint_suffix"
// KeyAzureEnvironmentName is the secret data key for the Azure cloud environment name.
KeyAzureEnvironmentName = "environment"
// KeyGCPStorageBucketName is the secret data key for the GCS bucket name.
KeyGCPStorageBucketName = "bucketname"
// KeyGCPServiceAccountKeyFilename is the service account key filename containing the Google authentication credentials.
KeyGCPServiceAccountKeyFilename = "key.json"
// KeySwiftAuthURL is the secret data key for the OpenStack Swift authentication URL.
KeySwiftAuthURL = "auth_url"
// KeySwiftContainerName is the secret data key for the OpenStack Swift container name.
KeySwiftContainerName = "container_name"
// KeySwiftDomainID is the secret data key for the OpenStack domain ID.
KeySwiftDomainID = "domain_id"
// KeySwiftDomainName is the secret data key for the OpenStack domain name.
KeySwiftDomainName = "domain_name"
// KeySwiftPassword is the secret data key for the OpenStack Swift password.
KeySwiftPassword = "password"
// KeySwiftProjectDomainId is the secret data key for the OpenStack project's domain id.
KeySwiftProjectDomainId = "project_domain_id"
// KeySwiftProjectDomainName is the secret data key for the OpenStack project's domain name.
KeySwiftProjectDomainName = "project_domain_name"
// KeySwiftProjectID is the secret data key for the OpenStack project id.
KeySwiftProjectID = "project_id"
// KeySwiftProjectName is the secret data key for the OpenStack project name.
KeySwiftProjectName = "project_name"
// KeySwiftRegion is the secret data key for the OpenStack Swift region.
KeySwiftRegion = "region"
// KeySwiftUserDomainID is the secret data key for the OpenStack Swift user domain id.
KeySwiftUserDomainID = "user_domain_id"
// KeySwiftUserDomainID is the secret data key for the OpenStack Swift user domain name.
KeySwiftUserDomainName = "user_domain_name"
// KeySwiftUserID is the secret data key for the OpenStack Swift user id.
KeySwiftUserID = "user_id"
// KeySwiftPassword is the secret data key for the OpenStack Swift password.
KeySwiftUsername = "username"
secretDirectory = "/etc/storage/secrets"
storageTLSVolume = "storage-tls"
caDirectory = "/etc/storage/ca"
)

@ -76,6 +76,8 @@ const (
AnnotationCertRotationRequiredAt string = "loki.grafana.com/certRotationRequiredAt"
// AnnotationLokiConfigHash stores the last SHA1 hash of the loki configuration
AnnotationLokiConfigHash string = "loki.grafana.com/config-hash"
// AnnotationLokiObjectStoreHash stores the last SHA1 hash of the loki object storage credetials.
AnnotationLokiObjectStoreHash string = "loki.grafana.com/object-store-hash"
// LabelCompactorComponent is the label value for the compactor component
LabelCompactorComponent string = "compactor"
@ -130,11 +132,18 @@ var (
volumeFileSystemMode = corev1.PersistentVolumeFilesystem
)
func commonAnnotations(configHash, rotationRequiredAt string) map[string]string {
return map[string]string{
AnnotationLokiConfigHash: configHash,
func commonAnnotations(configHash, objStoreHash, rotationRequiredAt string) map[string]string {
a := map[string]string{
AnnotationLokiConfigHash: configHash,
AnnotationCertRotationRequiredAt: rotationRequiredAt,
}
if objStoreHash != "" {
a[AnnotationLokiObjectStoreHash] = objStoreHash
}
return a
}
func commonLabels(stackName string) map[string]string {

Loading…
Cancel
Save