-
Notifications
You must be signed in to change notification settings - Fork 105
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
Ignore k8s secret not found when ESS enabled #492
Conversation
Signed-off-by: ezgidemirel <[email protected]>
@@ -93,7 +93,7 @@ func buildClient(ctx context.Context, local client.Client, cfg v1.SecretStoreCon | |||
// ReadKeyValues reads and returns key value pairs for a given Kubernetes Secret. | |||
func (ss *SecretStore) ReadKeyValues(ctx context.Context, n store.ScopedName, s *store.Secret) error { | |||
ks := &corev1.Secret{} | |||
if err := ss.client.Get(ctx, types.NamespacedName{Name: n.Name, Namespace: ss.namespaceForSecret(n)}, ks); err != nil { | |||
if err := ss.client.Get(ctx, types.NamespacedName{Name: n.Name, Namespace: ss.namespaceForSecret(n)}, ks); resource.IgnoreNotFound(err) != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not found, then what would be the value of ks.Data
?
could we add a unit test for the new behavior?
If ESS is not enabled, then should we return the error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not found, then what would be the value of
ks.Data
?
It will be an empty map.
could we add a unit test for the new behavior?
Sure, will update the PR.
If ESS is not enabled, then should we return the error?
This code block is only called when ESS is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be an empty map.
Can we update the functions docs to reflect that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be an empty map.
Can we update the functions docs to reflect that?
It shouldn't be necessary. In real world, we shouldn't end up with an empty secret. This change is done to proceed with an intermediate stage where the claim/composite secret is not created yet but it's fetched by the PTF composer to observe the state. Later in the reconciliation loop, the secret will be created as usual and we don't need to handle not found errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe making it more explicit would be helpful, something like:
if err := ss.client.Get(ctx, types.NamespacedName{Name: n.Name, Namespace: ss.namespaceForSecret(n)}, ks); resource.IgnoreNotFound(err) != nil { | |
err := ss.client.Get(ctx, types.NamespacedName{Name: n.Name, Namespace: ss.namespaceForSecret(n)}, ks) | |
if kerrors.IsNotFound(err) { | |
return nil | |
} | |
if err != nil { | |
return errors.Wrap(err, errGetSecret) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or is the caller relying on the fact that Metadata is set even on not found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we cannot find the secret on the store, we should overwrite the given secret fields with empty/nil values.
args: args{ | ||
client: resource.ClientApplicator{ | ||
Client: &test.MockClient{ | ||
MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should return error only if we try to retrieve the secret with the name equal to fakeSecretName
. Otherwise, if we do not perform this check, this tests is going to pass even if ReadKeyValues
does not properly use the passed ScopedName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's the issue with mocking, the mock is never going to be 100% precise with the real behaviour. I don't think it's strictly required here, but could be a separate test case maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mocks should react for only specific inputs. We are adding here the new test, so why not making it more tight here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pedjak , I think the SecretNotFound
test does exactly what it needs to do. I can update the SuccessfulRead
test with ScopedName
check if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you drop fakeSecretName
from the setup, and have something like this:
n: store.ScopedName{}
the test is still going to pass.
The mock should return the error only for the given secret name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it feels a bit overkill tbh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewMockGetFn
function does not use the object key. Which means we cannot check the scope name with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewMockGetFn function does not use the object key. Which means we cannot check the scope name with that.
yeah, but we can do something like:
Client: &test.MockClient{
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
if key.Name == fakeSecretName {
return kerrors.NewNotFound(schema.GroupResource{}, "")
}
return errors.Errorf("Should return not found error actually for %s", key)
},
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we can. But my point was, NewMockGetFn
function is used for dozens of places without actually checking the name or namespace and I think it's OK. IMHO, if we're mocking an interface, we don't need to worry about the mocker itself. The critical part is checking the behavior after getting the result from the mock.
Anyways, I updated the SuccessfulRead
test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we can. But my point was, NewMockGetFn function is used for dozens of places without actually checking the name or namespace and I think it's OK. IMHO, if we're mocking an interface, we don't need to worry about the mocker itself. The critical part is checking the behavior after getting the result from the mock.
We are not mocking just interface, we are actually mocking its invocations, and we in essence should define a number of invocations as set of (input params, results).
The code under tests should invoke an interface with proper input params, and should react properly on the obtained results. If we do not check the input params, then we verify just partially the code behavior.
}, | ||
}, | ||
want: want{ | ||
result: nil, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the test name says that return an empty secret
, should then the result be an empty map? If it should remain nil
, then perhaps we should rephrase the reason above to reflect that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we want
is not the secret, but the error. Therefore, the test is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we
want
is not the secret, but the error. Therefore, the test is correct
Nevermind, what I thought is not the one I wrote. I'll update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that I follow: in this test ReadKeyValues
is not going to return the error, and then we compare secrets data with want.result
. Do we consider an empty secret the one with Data
field being set to nil
or to an empty map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The zero value of a map is nil. I observed different behaviors like while printing it, I get map[]
but in the cmp.Diff
method, it considers it as a nil
.
Signed-off-by: ezgidemirel <[email protected]>
Description of your changes
In this change, we're ignoring k8s secret not found errors when the external secret store feature is enabled. In Vault implementation, we're not returning any error if the secret doesn't exist. To have the same behavior and allow secret creation at later stages, we start not to return any error in the k8s store as well. This is the root cause of this bug.
Fixes #
I have:
make reviewable test
to ensure this PR is ready for review.How has this code been tested