Skip to content

Commit

Permalink
Add EventRecorder abstraction.
Browse files Browse the repository at this point in the history
  • Loading branch information
Luca Palmieri committed Oct 16, 2021
1 parent 88c5616 commit 1fb72d2
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 0 deletions.
22 changes: 22 additions & 0 deletions kube-runtime/src/event_recorder/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::event_recorder::EventType;

/// Required information to publish a new event via [`EventRecorder::publish`].
///
/// [`EventRecorder::publish`]: crate::event_recorder::EventRecorder::publish
pub struct NewEvent {
/// The action that was taken (either successfully or unsuccessfully) against
/// the references object.
///
/// `action` must be machine-readable.
pub action: String,
/// The reason explaining why the `action` was taken.
///
/// `reason` must be human-readable.
pub reason: String,
/// A optional description of the status of the `action`.
///
/// `note` must be human-readable.
pub note: Option<String>,
/// The event severity.
pub event_type: EventType,
}
24 changes: 24 additions & 0 deletions kube-runtime/src/event_recorder/event_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::event_recorder::InstanceName;

/// Details about the event emitter.
///
/// ```rust
/// use std::convert::TryInto;
/// use kube_runtime::event_recorder::EventSource;
///
/// let event_source = EventSource {
/// instance_name: "my-awesome-controller-abcdef".try_into().unwrap(),
/// controller_name: "my-awesome-controller".into(),
/// };
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventSource {
/// The name of the controller publishing the event.
///
/// E.g. `my-awesome-controller`.
pub controller_name: String,
/// The name of the controller instance publishing the event.
///
/// E.g. `my-awesome-controller-abcdef`.
pub instance_name: InstanceName,
}
8 changes: 8 additions & 0 deletions kube-runtime/src/event_recorder/event_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// The event severity or type.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum EventType {
/// An event took place - nothing to worry about.
Normal,
/// Something is not working as expected - it might be worth to have a look.
Warning,
}
53 changes: 53 additions & 0 deletions kube-runtime/src/event_recorder/instance_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::convert::TryFrom;

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
/// The name of the controller instance publishing the event.
///
/// ```rust
/// use std::convert::TryInto;
/// use kube_runtime::event_recorder::InstanceName;
///
/// let instance_name: InstanceName = "my-awesome-controller-abcdef".try_into().unwrap();
/// ```
///
/// It must be:
///
/// - shorter than 128 characters.
pub struct InstanceName(String);

impl TryFrom<&str> for InstanceName {
type Error = String;

fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_from(value.to_string())
}
}

impl TryFrom<String> for InstanceName {
type Error = String;

fn try_from(v: String) -> Result<Self, Self::Error> {
// Limit imposed by Kubernetes' API
let n_chars = v.chars().count();
if n_chars > 128 {
Err(format!(
"The reporting instance name must be shorter than 128 characters.\n{} is {} characters long.",
v, n_chars
))
} else {
Ok(Self(v))
}
}
}

impl AsRef<str> for InstanceName {
fn as_ref(&self) -> &str {
&self.0
}
}

impl Into<String> for InstanceName {
fn into(self) -> String {
self.0
}
}
12 changes: 12 additions & 0 deletions kube-runtime/src/event_recorder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub use event::NewEvent;
pub use event_source::EventSource;
pub use event_type::EventType;
pub use instance_name::InstanceName;
pub use recorder::EventRecorder;

mod event;
mod event_source;
mod event_type;
mod instance_name;
mod recorder;

117 changes: 117 additions & 0 deletions kube-runtime/src/event_recorder/recorder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use k8s_openapi::chrono::Utc;
use k8s_openapi::api::core::v1::ObjectReference;
use k8s_openapi::api::events::v1::Event;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{MicroTime, ObjectMeta};
use kube::api::PostParams;
use kube::{Api, Client};
use crate::event_recorder::{EventSource, EventType};
use crate::event_recorder::event::NewEvent;

#[derive(Clone)]
/// A publisher abstraction to emit Kubernetes' events.
///
/// All events emitted by an `EventRecorder` are attached to the [`ObjectReference`]
/// specified when building the recorder using [`EventRecorder::new`].
///
/// ```rust
/// use std::convert::TryInto;
/// use kube_runtime::event_recorder::{EventSource, EventRecorder, NewEvent, EventType};
/// use k8s_openapi::api::core::v1::ObjectReference;
///
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let k8s_client: kube::Client = todo!();
/// let event_source = EventSource {
/// instance_name: "my-awesome-controller-abcdef".try_into().unwrap(),
/// controller_name: "my-awesome-controller".into(),
/// };
///
/// // You can populate this using `ObjectMeta` and `ApiResource` information
/// // from the object you are working with.
/// let object_reference = ObjectReference {
/// // [...]
/// # ..Default::default()
/// };
///
/// let event_recorder = EventRecorder::new(k8s_client, event_source, object_reference);
/// event_recorder.publish(NewEvent {
/// action: "Scheduling".into(),
/// reason: "Pulling".into(),
/// note: Some("Pulling image `nginx`".into()),
/// event_type: EventType::Normal
/// }).await?;
/// # Ok(())
/// # }
/// ```
///
/// Events attached to an object will be shown in the `Events` section of the output of
/// of `kubectl describe` for that object.
pub struct EventRecorder {
event_client: Api<Event>,
event_source: EventSource,
object_reference: ObjectReference,
}

impl EventRecorder {
/// Build a new [`EventRecorder`] instance to emit events attached to the
/// specified [`ObjectReference`].
pub fn new(
k8s_client: Client,
event_source: EventSource,
object_reference: ObjectReference,
) -> Self {
let event_client = match object_reference.namespace.as_ref() {
None => Api::all(k8s_client),
Some(namespace) => Api::namespaced(k8s_client, namespace),
};
Self {
event_client,
event_source,
object_reference,
}
}

/// Publish a new Kubernetes' event.
///
/// # Access control
///
/// The event object is created in the same namespace of the [`ObjectReference`]
/// you specified in [`EventRecorder::new`].
/// Make sure that your controller has `create` permissions in the required namespaces
/// for the `event` resource in the API group `events.k8s.io`.
pub async fn publish(
&self,
new_event: NewEvent,
) -> Result<(), kube::Error> {
self
.event_client
.create(
&PostParams::default(),
&Event {
action: Some(new_event.action),
reason: Some(new_event.reason),
deprecated_count: None,
deprecated_first_timestamp: None,
deprecated_last_timestamp: None,
deprecated_source: None,
event_time: MicroTime(Utc::now()),
regarding: Some(self.object_reference.clone()),
note: new_event.note,
metadata: ObjectMeta {
namespace: Some(self.object_reference.namespace.clone().unwrap()),
generate_name: Some(format!("{}-", self.event_source.controller_name)),
..Default::default()
},
reporting_controller: Some(self.event_source.controller_name.clone()),
reporting_instance: Some(self.event_source.instance_name.clone().into()),
series: None,
type_: match new_event.event_type {
EventType::Normal => Some("Normal".into()),
EventType::Warning => Some("Warning".into()),
},
related: None,
},
)
.await?;
Ok(())
}
}
1 change: 1 addition & 0 deletions kube-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#![allow(clippy::semicolon_if_nothing_returned)]

pub mod controller;
pub mod event_recorder;
pub mod finalizer;
pub mod reflector;
pub mod scheduler;
Expand Down

0 comments on commit 1fb72d2

Please sign in to comment.