Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 wrap service account not found error #1477

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
664a51f
changes to derice minimum service account
rashmi43 Sep 9, 2024
75c5f7c
remove headers
rashmi43 Sep 9, 2024
24d93f1
add details about registry+v1 support
rashmi43 Sep 10, 2024
fdf7e9d
render yml correctly
rashmi43 Sep 10, 2024
ba9193d
Merge branch 'operator-framework:main' into main
rashmi43 Oct 8, 2024
4067d8c
Merge branch 'operator-framework:main' into main
rashmi43 Oct 10, 2024
a6b2ebc
Merge branch 'operator-framework:main' into main
rashmi43 Oct 16, 2024
e87203a
Merge branch 'operator-framework:main' into main
rashmi43 Nov 18, 2024
157cce0
sa not found
rashmi43 Nov 18, 2024
eabd252
Merge branch 'operator-framework:main' into sa-err-msg
rashmi43 Nov 18, 2024
6062677
Delete docs/drafts/derive-serviceaccount.md
rashmi43 Nov 18, 2024
738a59c
add custom sa not found
rashmi43 Nov 18, 2024
6ed7b58
remove unused imports
rashmi43 Nov 18, 2024
5d5d2de
Update tokengetter.go
rashmi43 Nov 18, 2024
815115e
Update tokengetter.go
rashmi43 Nov 18, 2024
49549f2
update error message string
rashmi43 Nov 19, 2024
aa4f6e9
pass sa name to error message
rashmi43 Nov 19, 2024
1bbb34a
Update tokengetter.go
rashmi43 Nov 19, 2024
5196201
wrap error message
rashmi43 Nov 19, 2024
e8e76c6
Update internal/authentication/tokengetter.go
rashmi43 Nov 20, 2024
3ef45b6
Update tokengetter.go
rashmi43 Nov 20, 2024
1334b69
Update clusterextension_controller.go
rashmi43 Nov 20, 2024
83e188c
add unit test cases
rashmi43 Nov 26, 2024
683db58
updates testcases
rashmi43 Nov 26, 2024
7893d90
reverting this change
rashmi43 Nov 26, 2024
bad6695
review comment
rashmi43 Nov 26, 2024
9c0df21
update error msg
rashmi43 Dec 17, 2024
5df5a1a
review comments incorporated
rashmi43 Dec 17, 2024
7b23746
Merge branch 'main' into sa-err-msg
rashmi43 Jan 25, 2025
452703d
review comments
rashmi43 Jan 25, 2025
100c39e
review comments
rashmi43 Jan 25, 2025
668e370
add unwrap function
rashmi43 Jan 27, 2025
f6e5d66
add import in the right section
rashmi43 Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions internal/authentication/tokengetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package authentication

import (
"context"
"fmt"
"sync"
"time"

authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
Expand All @@ -19,6 +21,21 @@ type TokenGetter struct {
mu sync.RWMutex
}

type ServiceAccountNotFoundError struct {
ServiceAccountName string // The name of the missing ServiceAccount.
ServiceAccountNamespace string // The namespace where the ServiceAccount should exist
Err error // The underlying error
}

func (e *ServiceAccountNotFoundError) Unwrap() error {
return e.Err
}

// Error implements the error interface for ServiceAccountNotFoundError.
func (e *ServiceAccountNotFoundError) Error() string {
return fmt.Sprintf("ServiceAccount \"%s\" not found in namespace \"%s\": Unable to authenticate with the Kubernetes cluster.", e.ServiceAccountName, e.ServiceAccountNamespace)
}

type TokenGetterOption func(*TokenGetter)

const (
Expand Down Expand Up @@ -86,6 +103,9 @@ func (t *TokenGetter) getToken(ctx context.Context, key types.NamespacedName) (*
Spec: authenticationv1.TokenRequestSpec{ExpirationSeconds: ptr.To(int64(t.expirationDuration / time.Second))},
}, metav1.CreateOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil, &ServiceAccountNotFoundError{ServiceAccountName: key.Name, ServiceAccountNamespace: key.Namespace}
}
return nil, err
}
return &req.Status, nil
Expand Down
5 changes: 5 additions & 0 deletions internal/controllers/clusterextension_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (

ocv1 "github.com/operator-framework/operator-controller/api/v1"
catalogd "github.com/operator-framework/operator-controller/catalogd/api/v1"
"github.com/operator-framework/operator-controller/internal/authentication"
"github.com/operator-framework/operator-controller/internal/bundleutil"
"github.com/operator-framework/operator-controller/internal/conditionsets"
"github.com/operator-framework/operator-controller/internal/contentmanager"
Expand Down Expand Up @@ -206,6 +207,10 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1.Cl
installedBundle, err := r.InstalledBundleGetter.GetInstalledBundle(ctx, ext)
if err != nil {
setInstallStatus(ext, nil)
var saerr *authentication.ServiceAccountNotFoundError
if errors.As(err, &saerr) {
err = saerr
}
setInstalledStatusConditionUnknown(ext, err.Error())
setStatusProgressing(ext, errors.New("retrying to get installed bundle"))
return ctrl.Result{}, err
Expand Down
83 changes: 83 additions & 0 deletions internal/controllers/clusterextension_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,89 @@ func TestClusterExtensionInstallationSucceeds(t *testing.T) {
require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{}))
}

func TestClusterExtensionInstallationFailsWithNoServiceAccount(t *testing.T) {
Copy link
Contributor

@camilamacedo86 camilamacedo86 Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @rashmi43,

Without diving into the code itself, I think we should improve how t.Log is being used. The messages should describe actions like "Validating the status to ensure it is X" or "Checking the progressing condition." This makes the logs more meaningful and helps anyone reading them understand what’s happening in the test.

Also, we can make things cleaner by using defer for resource cleanup. I would like to suggest something like:

// TestClusterExtensionInstallationFailsWithNoServiceAccount
// Validates that the reconciliation process fails gracefully when the specified 
// ServiceAccount does not exist. Ensures that appropriate conditions are set 
// on the ClusterExtension status, and the resource is cleaned up properly after the test.
func TestClusterExtensionInstallationFailsWithNoServiceAccount(t *testing.T) {
	cl, reconciler := newClientAndReconciler(t)

	reconciler.Unpacker = &MockUnpacker{
		result: &source.Result{
			State:  source.StateUnpacked,
			Bundle: fstest.MapFS{},
		},
	}

	ctx := context.Background()
	extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))}
	defer func() {
		t.Log("Cleaning up all cluster extension resources")
		cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{})
	}()

	t.Log("Initializing cluster extension with a channel, version, and non-existent service account")
	pkgName := "prometheus"
	pkgVer := "1.0.0"
	pkgChan := "beta"
	namespace := fmt.Sprintf("test-ns-%s", rand.String(8))
	serviceAccount := fmt.Sprintf("test-does-not-exist-%s", rand.String(8))

	clusterExtension := &ocv1.ClusterExtension{
		ObjectMeta: metav1.ObjectMeta{Name: extKey.Name},
		Spec: ocv1.ClusterExtensionSpec{
			Source: ocv1.SourceConfig{
				SourceType: "Catalog",
				Catalog: &ocv1.CatalogSource{
					PackageName: pkgName,
					Version:     pkgVer,
					Channels:    []string{pkgChan},
				},
			},
			Namespace: namespace,
			ServiceAccount: ocv1.ServiceAccountReference{
				Name: serviceAccount,
			},
		},
	}
	require.NoError(t, cl.Create(ctx, clusterExtension), "Failed to create the cluster extension resource")

	t.Log("Setting up mock resolver and applier for reconciliation")
	reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
		v := bsemver.MustParse("1.0.0")
		return &declcfg.Bundle{
			Name:    "prometheus.v1.0.0",
			Package: "prometheus",
			Image:   "quay.io/operatorhubio/[email protected]",
		}, &v, nil, nil
	})
	reconciler.Applier = &MockApplier{
		objs: []client.Object{},
	}
	reconciler.Manager = &MockManagedContentCacheManager{
		cache: &MockManagedContentCache{},
	}

	t.Log("Running reconcile to process the cluster extension")
	res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey})
	require.Equal(t, ctrl.Result{}, res, "Unexpected reconcile result")
	require.NoError(t, err, "Reconcile failed with an error")

	t.Log("Fetching updated cluster extension after reconciliation")
	require.NoError(t, cl.Get(ctx, extKey, clusterExtension), "Failed to retrieve the updated cluster extension resource")

	t.Log("Verifying the status fields of the cluster extension")
	require.Equal(t, ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle, "Bundle metadata does not match expected values")

	t.Log("Verifying the installed condition of the cluster extension")
	installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled)
	require.NotNil(t, installedCond, "Installed condition not found")
	require.Equal(t, metav1.ConditionTrue, installedCond.Status, "Installed condition status should be True")
	require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason, "Installed condition reason should indicate success")

	t.Log("Verifying the progressing condition of the cluster extension")
	progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
	require.NotNil(t, progressingCond, "Progressing condition not found")
	require.Equal(t, ocv1.ReasonFailed, progressingCond.Reason, "Progressing condition reason should indicate failure")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

going to stash these changes

cl, reconciler := newClientAndReconciler(t)
reconciler.Unpacker = &MockUnpacker{
result: &source.Result{
State: source.StateUnpacked,
Bundle: fstest.MapFS{},
},
}

ctx := context.Background()
extKey := types.NamespacedName{Name: fmt.Sprintf("cluster-extension-test-%s", rand.String(8))}

t.Log("When the cluster extension specifies a channel with version that exist")
t.Log("By initializing cluster state")
pkgName := "prometheus"
pkgVer := "1.0.0"
pkgChan := "beta"
namespace := fmt.Sprintf("test-ns-%s", rand.String(8))
serviceAccount := fmt.Sprintf("test-does1-not-exist-%s", rand.String(8))

clusterExtension := &ocv1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{Name: extKey.Name},
Spec: ocv1.ClusterExtensionSpec{
Source: ocv1.SourceConfig{
SourceType: "Catalog",
Catalog: &ocv1.CatalogSource{
PackageName: pkgName,
Version: pkgVer,
Channels: []string{pkgChan},
},
},
Namespace: namespace,
ServiceAccount: ocv1.ServiceAccountReference{
Name: serviceAccount,
},
},
}
err := cl.Create(ctx, clusterExtension)
require.NoError(t, err)

t.Log("It sets resolution success status")
t.Log("By running reconcile")
reconciler.Resolver = resolve.Func(func(_ context.Context, _ *ocv1.ClusterExtension, _ *ocv1.BundleMetadata) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) {
v := bsemver.MustParse("1.0.0")
return &declcfg.Bundle{
Name: "prometheus.v1.0.0",
Package: "prometheus",
Image: "quay.io/operatorhubio/[email protected]",
}, &v, nil, nil
})
reconciler.Applier = &MockApplier{
objs: []client.Object{},
}
reconciler.Manager = &MockManagedContentCacheManager{
cache: &MockManagedContentCache{},
}
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey})
require.Equal(t, ctrl.Result{}, res)
require.NoError(t, err)

t.Log("By fetching updated cluster extension after reconcile")
require.NoError(t, cl.Get(ctx, extKey, clusterExtension))

t.Log("By checking the status fields")
require.Equal(t, ocv1.BundleMetadata{Name: "prometheus.v1.0.0", Version: "1.0.0"}, clusterExtension.Status.Install.Bundle)
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: extKey})
require.Error(t, err, res)

t.Log("By checking the expected installed conditions")
installedCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled)
require.NotNil(t, installedCond)
require.Equal(t, metav1.ConditionTrue, installedCond.Status)
require.Equal(t, ocv1.ReasonSucceeded, installedCond.Reason)

t.Log("By checking the expected progressing conditions")
progressingCond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
require.NotNil(t, progressingCond)
require.Equal(t, metav1.ConditionTrue, progressingCond.Status)
require.Equal(t, ocv1.ReasonSucceeded, progressingCond.Reason)

require.NoError(t, cl.DeleteAllOf(ctx, &ocv1.ClusterExtension{}))
}

func TestClusterExtensionDeleteFinalizerFails(t *testing.T) {
cl, reconciler := newClientAndReconciler(t)
reconciler.Unpacker = &MockUnpacker{
Expand Down
Loading