Skip to content

Commit

Permalink
feat(operator): Allow setting explicit CredentialMode in LokiStack st…
Browse files Browse the repository at this point in the history
…orage spec (grafana#12106)
  • Loading branch information
xperimental authored and rhnasc committed Apr 12, 2024
1 parent 3ab6d1b commit bf4577b
Show file tree
Hide file tree
Showing 18 changed files with 601 additions and 212 deletions.
1 change: 1 addition & 0 deletions operator/CHANGELOG.md
@@ -1,5 +1,6 @@
## Main

- [12106](https://github.com/grafana/loki/pull/12106) **xperimental**: Allow setting explicit CredentialMode in LokiStack storage spec
- [11968](https://github.com/grafana/loki/pull/11968) **xperimental**: Extend status to show difference between running and ready
- [12007](https://github.com/grafana/loki/pull/12007) **xperimental**: Extend Azure secret validation
- [12008](https://github.com/grafana/loki/pull/12008) **xperimental**: Support using multiple buckets with AWS STS
Expand Down
8 changes: 8 additions & 0 deletions operator/apis/loki/v1/lokistack_types.go
Expand Up @@ -529,6 +529,14 @@ type ObjectStorageSecretSpec struct {
// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors="urn:alm:descriptor:io.kubernetes:Secret",displayName="Object Storage Secret Name"
Name string `json:"name"`

// CredentialMode can be used to set the desired credential mode for authenticating with the object storage.
// If this is not set, then the operator tries to infer the credential mode from the provided secret and its
// own configuration.
//
// +optional
// +kubebuilder:validation:Optional
CredentialMode CredentialMode `json:"credentialMode,omitempty"`
}

// ObjectStorageSchemaVersion defines the storage schema version which will be
Expand Down
Expand Up @@ -606,6 +606,16 @@ spec:
description: Secret for object storage authentication. Name of
a secret in the same namespace as the LokiStack custom resource.
properties:
credentialMode:
description: CredentialMode can be used to set the desired
credential mode for authenticating with the object storage.
If this is not set, then the operator tries to infer the
credential mode from the provided secret and its own configuration.
enum:
- static
- token
- managed
type: string
name:
description: Name of a secret in the namespace configured
for object storage secrets.
Expand Down
Expand Up @@ -606,6 +606,16 @@ spec:
description: Secret for object storage authentication. Name of
a secret in the same namespace as the LokiStack custom resource.
properties:
credentialMode:
description: CredentialMode can be used to set the desired
credential mode for authenticating with the object storage.
If this is not set, then the operator tries to infer the
credential mode from the provided secret and its own configuration.
enum:
- static
- token
- managed
type: string
name:
description: Name of a secret in the namespace configured
for object storage secrets.
Expand Down
Expand Up @@ -606,6 +606,16 @@ spec:
description: Secret for object storage authentication. Name of
a secret in the same namespace as the LokiStack custom resource.
properties:
credentialMode:
description: CredentialMode can be used to set the desired
credential mode for authenticating with the object storage.
If this is not set, then the operator tries to infer the
credential mode from the provided secret and its own configuration.
enum:
- static
- token
- managed
type: string
name:
description: Name of a secret in the namespace configured
for object storage secrets.
Expand Down
10 changes: 10 additions & 0 deletions operator/config/crd/bases/loki.grafana.com_lokistacks.yaml
Expand Up @@ -588,6 +588,16 @@ spec:
description: Secret for object storage authentication. Name of
a secret in the same namespace as the LokiStack custom resource.
properties:
credentialMode:
description: CredentialMode can be used to set the desired
credential mode for authenticating with the object storage.
If this is not set, then the operator tries to infer the
credential mode from the provided secret and its own configuration.
enum:
- static
- token
- managed
type: string
name:
description: Name of a secret in the namespace configured
for object storage secrets.
Expand Down
2 changes: 1 addition & 1 deletion operator/controllers/loki/lokistack_controller.go
Expand Up @@ -180,7 +180,7 @@ func (r *LokiStackReconciler) updateResources(ctx context.Context, req ctrl.Requ
}

if r.FeatureGates.OpenShift.ManagedAuthEnv {
if err := handlers.CreateCredentialsRequest(ctx, r.Log, r.Scheme, r.AuthConfig, r.Client, req); err != nil {
if err := handlers.CreateUpdateDeleteCredentialsRequest(ctx, r.Log, r.Scheme, r.AuthConfig, r.Client, req); err != nil {
return "", err
}
}
Expand Down
18 changes: 17 additions & 1 deletion operator/docs/operator/api.md
Expand Up @@ -1103,7 +1103,7 @@ string
## CredentialMode { #loki-grafana-com-v1-CredentialMode }
(<code>string</code> alias)
<p>
(<em>Appears on:</em><a href="#loki-grafana-com-v1-LokiStackStorageStatus">LokiStackStorageStatus</a>)
(<em>Appears on:</em><a href="#loki-grafana-com-v1-LokiStackStorageStatus">LokiStackStorageStatus</a>, <a href="#loki-grafana-com-v1-ObjectStorageSecretSpec">ObjectStorageSecretSpec</a>)
</p>
<div>
<p>CredentialMode represents the type of authentication used for accessing the object storage.</p>
Expand Down Expand Up @@ -2706,6 +2706,22 @@ string
<p>Name of a secret in the namespace configured for object storage secrets.</p>
</td>
</tr>
<tr>
<td>
<code>credentialMode</code><br/>
<em>
<a href="#loki-grafana-com-v1-CredentialMode">
CredentialMode
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>CredentialMode can be used to set the desired credential mode for authenticating with the object storage.
If this is not set, then the operator tries to infer the credential mode from the provided secret and its
own configuration.</p>
</td>
</tr>
</tbody>
</table>

Expand Down
39 changes: 37 additions & 2 deletions operator/internal/handlers/credentialsrequest.go
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/ViaQ/logerr/v2/kverrors"
"github.com/go-logr/logr"
cloudcredentialv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -19,9 +20,9 @@ import (
"github.com/grafana/loki/operator/internal/manifests/openshift"
)

// CreateCredentialsRequest creates a new CredentialsRequest resource for a Lokistack
// CreateUpdateDeleteCredentialsRequest creates a new CredentialsRequest resource for a Lokistack
// to request a cloud credentials Secret resource from the OpenShift cloud-credentials-operator.
func CreateCredentialsRequest(ctx context.Context, log logr.Logger, scheme *runtime.Scheme, managedAuth *config.ManagedAuthConfig, k k8s.Client, req ctrl.Request) error {
func CreateUpdateDeleteCredentialsRequest(ctx context.Context, log logr.Logger, scheme *runtime.Scheme, managedAuth *config.ManagedAuthConfig, k k8s.Client, req ctrl.Request) error {
ll := log.WithValues("lokistack", req.NamespacedName, "event", "createCredentialsRequest")

var stack lokiv1.LokiStack
Expand All @@ -34,6 +35,24 @@ func CreateCredentialsRequest(ctx context.Context, log logr.Logger, scheme *runt
return kverrors.Wrap(err, "failed to lookup LokiStack", "name", req.String())
}

if !hasManagedCredentialMode(&stack) {
// Operator is running in managed-mode, but stack specifies non-managed mode -> skip CredentialsRequest
var credReq cloudcredentialv1.CredentialsRequest
if err := k.Get(ctx, req.NamespacedName, &credReq); err != nil {
if apierrors.IsNotFound(err) {
// CredentialsRequest does not exist -> this is what we want
return nil
}

return kverrors.Wrap(err, "failed to lookup CredentialsRequest", "name", req.String())
}

if err := k.Delete(ctx, &credReq); err != nil {
return kverrors.Wrap(err, "failed to remove CredentialsRequest", "name", req.String())
}
return nil
}

opts := openshift.Options{
BuildOpts: openshift.BuildOptions{
LokiStackName: stack.Name,
Expand Down Expand Up @@ -71,3 +90,19 @@ func CreateCredentialsRequest(ctx context.Context, log logr.Logger, scheme *runt

return nil
}

// hasManagedCredentialMode returns true, if the LokiStack is configured to run in managed-mode.
// This function assumes only being called when the operator is running in managed-credentials mode, so it is
// only returning false when the credential-mode has been explicitly configured, even if the target storage
// is not supporting managed-mode at all.
func hasManagedCredentialMode(stack *lokiv1.LokiStack) bool {
switch stack.Spec.Storage.Secret.CredentialMode {
case lokiv1.CredentialModeStatic, lokiv1.CredentialModeToken:
return false
case lokiv1.CredentialModeManaged:
return true
default:
}

return true
}
88 changes: 82 additions & 6 deletions operator/internal/handlers/credentialsrequest_test.go
Expand Up @@ -39,7 +39,7 @@ func credentialsRequestFakeClient(cr *cloudcredentialv1.CredentialsRequest, loki
return k
}

func TestCreateCredentialsRequest_CreateNewResource(t *testing.T) {
func TestCreateUpdateDeleteCredentialsRequest_CreateNewResource(t *testing.T) {
wantServiceAccountNames := []string{
"my-stack",
"my-stack-ruler",
Expand All @@ -63,7 +63,7 @@ func TestCreateCredentialsRequest_CreateNewResource(t *testing.T) {
},
}

err := CreateCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
err := CreateUpdateDeleteCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
require.NoError(t, err)
require.Equal(t, 1, k.CreateCallCount())

Expand All @@ -74,7 +74,7 @@ func TestCreateCredentialsRequest_CreateNewResource(t *testing.T) {
require.Equal(t, wantServiceAccountNames, credReq.Spec.ServiceAccountNames)
}

func TestCreateCredentialsRequest_CreateNewResourceAzure(t *testing.T) {
func TestCreateUpdateDeleteCredentialsRequest_CreateNewResourceAzure(t *testing.T) {
wantRegion := "test-region"

lokistack := &lokiv1.LokiStack{
Expand All @@ -98,7 +98,7 @@ func TestCreateCredentialsRequest_CreateNewResourceAzure(t *testing.T) {
},
}

err := CreateCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
err := CreateUpdateDeleteCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
require.NoError(t, err)

require.Equal(t, 1, k.CreateCallCount())
Expand All @@ -112,7 +112,7 @@ func TestCreateCredentialsRequest_CreateNewResourceAzure(t *testing.T) {
require.Equal(t, wantRegion, providerSpec.AzureRegion)
}

func TestCreateCredentialsRequest_DoNothing_WhenCredentialsRequestExist(t *testing.T) {
func TestCreateUpdateDeleteCredentialsRequest_Update_WhenCredentialsRequestExist(t *testing.T) {
req := ctrl.Request{
NamespacedName: client.ObjectKey{Name: "my-stack", Namespace: "ns"},
}
Expand All @@ -138,9 +138,85 @@ func TestCreateCredentialsRequest_DoNothing_WhenCredentialsRequestExist(t *testi

k := credentialsRequestFakeClient(cr, lokistack)

err := CreateCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
err := CreateUpdateDeleteCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
require.NoError(t, err)
require.Equal(t, 2, k.GetCallCount())
require.Equal(t, 0, k.CreateCallCount())
require.Equal(t, 1, k.UpdateCallCount())
}

func TestCreateUpdateDeleteCredentialsRequest_DeleteExisting_WhenNotManagedMode(t *testing.T) {
req := ctrl.Request{
NamespacedName: client.ObjectKey{Name: "my-stack", Namespace: "ns"},
}

managedAuth := &config.ManagedAuthConfig{
AWS: &config.AWSEnvironment{
RoleARN: "a-role-arn",
},
}

cr := &cloudcredentialv1.CredentialsRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "my-stack",
Namespace: "ns",
},
}
lokistack := &lokiv1.LokiStack{
ObjectMeta: metav1.ObjectMeta{
Name: "my-stack",
Namespace: "ns",
},
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Secret: lokiv1.ObjectStorageSecretSpec{
CredentialMode: lokiv1.CredentialModeStatic,
},
},
},
}

k := credentialsRequestFakeClient(cr, lokistack)

err := CreateUpdateDeleteCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
require.NoError(t, err)
require.Equal(t, 2, k.GetCallCount())
require.Equal(t, 0, k.CreateCallCount())
require.Equal(t, 0, k.UpdateCallCount())
require.Equal(t, 1, k.DeleteCallCount())
}

func TestCreateUpdateDeleteCredentialsRequest_DoNothing_WhenNotManagedMode(t *testing.T) {
req := ctrl.Request{
NamespacedName: client.ObjectKey{Name: "my-stack", Namespace: "ns"},
}

managedAuth := &config.ManagedAuthConfig{
AWS: &config.AWSEnvironment{
RoleARN: "a-role-arn",
},
}

lokistack := &lokiv1.LokiStack{
ObjectMeta: metav1.ObjectMeta{
Name: "my-stack",
Namespace: "ns",
},
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Secret: lokiv1.ObjectStorageSecretSpec{
CredentialMode: lokiv1.CredentialModeStatic,
},
},
},
}

k := credentialsRequestFakeClient(nil, lokistack)

err := CreateUpdateDeleteCredentialsRequest(context.Background(), logger, scheme, managedAuth, k, req)
require.NoError(t, err)
require.Equal(t, 2, k.GetCallCount())
require.Equal(t, 0, k.CreateCallCount())
require.Equal(t, 0, k.UpdateCallCount())
require.Equal(t, 0, k.DeleteCallCount())
}

0 comments on commit bf4577b

Please sign in to comment.