Skip to content

Conversation

VenkatRamaraju
Copy link
Contributor

@VenkatRamaraju VenkatRamaraju commented Sep 13, 2021

k8s_events.py: Allows users to more easily emit events for their managed objects.

Moved the find_resource and get_api_client functions from k8s_status.py to api_utils.py so that both k8s_events.py and k8s_status.py can leverage those functions without redundancy.

@VenkatRamaraju VenkatRamaraju force-pushed the master branch 2 times, most recently from 27ae229 to 598f13e Compare September 14, 2021 16:58
return argumentSpec

def execute_module(self):
self.client = DynamicClient(kubernetes.config.new_client_from_config())
Copy link
Member

Choose a reason for hiding this comment

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

This is not going to cover enough cases, see the client construction in https://github.com/operator-framework/operator-sdk-ansible-util/blob/master/plugins/modules/k8s_status.py#L281-L342

Probably the best thing to do would be to move that function from k8s_status to a new file under module_utils, and then import it from both (you could do the same with find_resource)

Copy link
Member

Choose a reason for hiding this comment

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

Still need to remove this line

Copy link
Member

Choose a reason for hiding this comment

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

bump

Copy link
Contributor Author

@VenkatRamaraju VenkatRamaraju Sep 23, 2021

Choose a reason for hiding this comment

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

forgot to remove this line. Just did.


# Add more fields
if self.params['setTimestamp']:
self.params["name"] = self.params["name"] + "-" + str(now)
Copy link
Member

Choose a reason for hiding this comment

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

I think they usually append it with a . rather than a -

involvedObject:
type: dict
description: The object that this event is about.
setTimestamp:
Copy link
Member

Choose a reason for hiding this comment

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

appendTimestamp might be a more fitting name

setTimestamp:
type: bool
description: Event name should have timestamp appended to it
api_key:
Copy link
Member

Choose a reason for hiding this comment

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

I think all these are actually documented in the auth doc fragment, see how the k8s_status module uses them (https://github.com/operator-framework/operator-sdk-ansible-util/blob/master/plugins/modules/k8s_status.py#L29-L31)

"type": self.params.get("type"),
}

self.exit_json(**event)
Copy link
Member

Choose a reason for hiding this comment

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

I think you're returning without actually sending the event object to the API

@VenkatRamaraju VenkatRamaraju force-pushed the master branch 3 times, most recently from 3dead8d to 8919e72 Compare September 17, 2021 16:52
@@ -1,0 +1,88 @@
import kubernetes
Copy link
Member

Choose a reason for hiding this comment

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

I'd put these into a more descriptively named file, even if it's just api_utils.py or something

extends_documentation_fragment:
- operator_sdk.util.osdk_auth_options
- operator_sdk.util.osdk_name_options
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you want the name fragment included, that would add api_version and kind and name and namespace are double documented

Copy link
Member

Choose a reason for hiding this comment

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

bump

return argumentSpec

def execute_module(self):
self.client = DynamicClient(kubernetes.config.new_client_from_config())
Copy link
Member

Choose a reason for hiding this comment

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

Still need to remove this line

import copy
import datetime
import traceback
import plugins.module_utils as moduleUtils
Copy link
Member

Choose a reason for hiding this comment

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

you'll need to import it like the other module_utils on line 176, ie

     from ansible_collections.operator_sdk.util.plugins.module_utils.args_common import (AUTH_ARG_SPEC)

and it will need to be inside that try block, which prevents the module from failing on initialization (we want them to fail on execution so that we can report the message to the user)

return argumentSpec

def execute_module(self):
self.client = moduleUtils.get_api_client(None)
Copy link
Member

Choose a reason for hiding this comment

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

you'll want to pass self in so that the function can pull the proper variables out of the module

@VenkatRamaraju VenkatRamaraju force-pushed the master branch 3 times, most recently from 08f0716 to f848f17 Compare September 20, 2021 04:43
Comment on lines 289 to 295
instance = self.client.resources.get(api_version='v1', kind='Event')
try:
instance.patch(body=event)
except openshift.dynamic.exceptions.NotFoundError:
instance.create(body=event)

return instance
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fabianvf - Just wanted to make sure this is how the object is sent to the API. Let me know if any changes need to be made.

Copy link
Member

Choose a reason for hiding this comment

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

you'll want to return what you've documented you'll return here: https://github.com/operator-framework/operator-sdk-ansible-util/pull/22/files#diff-b2c14be32fda6eb09623e5d6d9be6350eaea5261d3c51eb8c6267fa653bc8072R131-R166

You'll also need to return whether the task resulted in a change or not (for Ansible), see the k8s_status module: https://github.com/operator-framework/operator-sdk-ansible-util/blob/master/plugins/modules/k8s_status.py#L465

so by returning instance what you're actually doing is returning the v1.Event api, what you want to return is the result of the patch or create. You'll also need to get the object from the api so that you can compare it to the result of the patch (if they are identical then you'd return changed=False. So the logic would look more like (this is pseudocode):

v1_events = self.client.resources.get(api_version='v1', kind='Event')
try:
    instance = v1_events.get(name=name, namespace=namespace)
except openshift.dynamic.exceptions.NotFoundError:
    result = instance.create(body=event)
    return dict(result=result.to_dict(), changed=True)

result = instance.patch(body=event)
changed = some_comparison_method(instance, result)
return dict(result=result.to_dict(), changed=changed)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, just updated the documentation return section to match the event object.

Also, do you you mean v1_events.create(body=event) in the Except block? I don't think we can reference instance in the Except block if it is declared in the try block. If yes, I just made those changes.

"type": self.params.get("type"),
}

instance = self.client.resources.get(api_version='v1', kind='Event')
Copy link
Member

Choose a reason for hiding this comment

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

instance is not really true, this is not an instance of an event, this is actually the api client for the v1.Event api

Comment on lines 289 to 295
instance = self.client.resources.get(api_version='v1', kind='Event')
try:
instance.patch(body=event)
except openshift.dynamic.exceptions.NotFoundError:
instance.create(body=event)

return instance
Copy link
Member

Choose a reason for hiding this comment

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

you'll want to return what you've documented you'll return here: https://github.com/operator-framework/operator-sdk-ansible-util/pull/22/files#diff-b2c14be32fda6eb09623e5d6d9be6350eaea5261d3c51eb8c6267fa653bc8072R131-R166

You'll also need to return whether the task resulted in a change or not (for Ansible), see the k8s_status module: https://github.com/operator-framework/operator-sdk-ansible-util/blob/master/plugins/modules/k8s_status.py#L465

so by returning instance what you're actually doing is returning the v1.Event api, what you want to return is the result of the patch or create. You'll also need to get the object from the api so that you can compare it to the result of the patch (if they are identical then you'd return changed=False. So the logic would look more like (this is pseudocode):

v1_events = self.client.resources.get(api_version='v1', kind='Event')
try:
    instance = v1_events.get(name=name, namespace=namespace)
except openshift.dynamic.exceptions.NotFoundError:
    result = instance.create(body=event)
    return dict(result=result.to_dict(), changed=True)

result = instance.patch(body=event)
changed = some_comparison_method(instance, result)
return dict(result=result.to_dict(), changed=changed)

Copy link
Member

@fabianvf fabianvf left a comment

Choose a reason for hiding this comment

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

You'll also need to add some end-to-end molecule tests that exercise this module to the molecule/default scenario (create a new task file in molecule/default/tasks and import it from molecule/default/converge.yml)

try:
instance = v1_events.get(name=self.params.get("name"), namespace=self.params.get("namespace"))
except openshift.dynamic.exceptions.NotFoundError:
result = v1_events.create(body=event, namespace=self.params.get("namespace"))
Copy link
Member

Choose a reason for hiding this comment

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

we'll also want to wrap the api calls in try...except blocks so we can fail gracefully


result = instance.patch(body=event, namespace=self.params.get("namespace"))
changed = instance.to_dict() == result.to_dict()
return dict(result=result.to_dict(), changed=changed)
Copy link
Member

Choose a reason for hiding this comment

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

to_dict is a little expensive so we should only call it once

@VenkatRamaraju VenkatRamaraju force-pushed the master branch 2 times, most recently from 4e81714 to f3f1260 Compare September 22, 2021 16:31
Comment on lines 256 to 270

if self.params['appendTimestamp']:
event = {
"kind": "Event",
"eventTime": None,
"message": self.params.get("message"),
"metadata": metadata,
"reason": self.params.get("reason"),
"reportingComponent": self.params.get("reportingComponent"),
"source": self.params.get("source"),
"type": self.params.get("type"),
}

created_event = v1_events.create(body=event, namespace=self.params.get("namespace"))
return dict(result=created_event.to_dict(), changed=True)
Copy link
Contributor Author

@VenkatRamaraju VenkatRamaraju Sep 22, 2021

Choose a reason for hiding this comment

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

@fabianvf - just wanted to make sure you saw this change. I created a new event and instantly returned it if the appendTimestamp is set to true, since it'll be unique in that case.


- assert:
that:
- event_obj.name == 'test-name'
Copy link
Member

Choose a reason for hiding this comment

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

Rather than asserting these on the event returned by the task, you should use the community.kubernetes.k8s_info module to retrieve the event from the API and run the asserts on that response (just to ensure the new module can't interfere with the assertions with incorrect return values or something like that)

extends_documentation_fragment:
- operator_sdk.util.osdk_auth_options
- operator_sdk.util.osdk_name_options
Copy link
Member

Choose a reason for hiding this comment

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

bump

"name": {"type": "str", "required": True},
"namespace": {"type": "str", "required": True},
},
"api": {},
Copy link
Member

Choose a reason for hiding this comment

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

What is this field used for?

"namespace": {"type": "str", "required": True},
},
"api": {},
"api_version": {"default": "v1"},
Copy link
Member

Choose a reason for hiding this comment

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

you don't need these fields, the only valid api_version is v1 and the only valid kind is Event

return argumentSpec

def execute_module(self):
self.client = DynamicClient(kubernetes.config.new_client_from_config())
Copy link
Member

Choose a reason for hiding this comment

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

bump

if self.params['appendTimestamp']:
event = {
"kind": "Event",
"eventTime": None,
Copy link
Member

Choose a reason for hiding this comment

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

We should probably be setting this

"type": self.params.get("type"),
}

created_event = v1_events.create(body=event, namespace=self.params.get("namespace"))
Copy link
Member

Choose a reason for hiding this comment

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

this'll need to be in a try .. except block

event = {
"kind": "Event",
"count": prior_count,
"eventTime": None,
Copy link
Member

Choose a reason for hiding this comment

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

here too

except Exception as err:
self.fail_json(msg="Unable to create event: {0}".format(err))

result = v1_events.patch(body=event, namespace=self.params.get("namespace"))
Copy link
Member

Choose a reason for hiding this comment

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

This will also need to be in a try ... except block

@VenkatRamaraju
Copy link
Contributor Author

Addressed all changes, working on molecule tests now.

@VenkatRamaraju VenkatRamaraju force-pushed the master branch 4 times, most recently from c6f1e11 to 84fb24e Compare September 24, 2021 17:57
@fabianvf fabianvf merged commit c6a9d40 into operator-framework:master Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants