1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
//! Types representing CloudFormation stack events.
#![allow(clippy::module_name_repetitions)]

use chrono::{DateTime, Utc};

use crate::{ResourceStatus, StackStatus, Status};

/// A stack event from the `DescribeStackEvents` API.
///
/// Stack events are represented as an enum because the API reports both events for the stack and
/// events for the resources in the stack, but these can have different sets of statuses.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StackEvent {
    /// An event for the stack itself.
    Stack {
        /// Current status of the stack.
        resource_status: StackStatus,

        /// The details of the event.
        details: StackEventDetails,
    },
    Resource {
        /// Current status of the resource.
        resource_status: ResourceStatus,

        /// The details of the event.
        details: StackEventDetails,
    },
}

impl StackEvent {
    /// Get the resource status of the event.
    ///
    /// This returns a trait object. You can match on `self` if you need the specific status type.
    #[must_use]
    pub fn resource_status(&self) -> &dyn Status {
        match self {
            Self::Stack {
                resource_status, ..
            } => resource_status,
            Self::Resource {
                resource_status, ..
            } => resource_status,
        }
    }

    /// Get the details of the event.
    #[must_use]
    pub fn details(&self) -> &StackEventDetails {
        match self {
            Self::Stack { details, .. } | Self::Resource { details, .. } => details,
        }
    }

    /// Get the token passed to the operation that generated this event.
    ///
    /// All events triggerd by a given stack operation are assigne dthe same client request token,
    /// you can use to track operations. For example, if you execute a `CreateStack` operation with
    /// the token `token1`, then all the `StackEvents` generated by that operation will have
    /// `ClientRequestToken` set as `token1`.
    ///
    /// In the console, stack operations display the client request token on the Events tab. Stack
    /// operations that are initiated from the console use the token format
    /// *Console-StackOperation-ID*, which helps you easily identify the stack operation. For
    /// example, if you create a stack using the console, each stack event would be assigned the
    /// same token in the following format:
    /// `Console-CreateStack-7f59c3cf-00d2-40c7-b2ff-e75db0987002`.
    #[must_use]
    pub fn client_request_token(&self) -> Option<&str> {
        self.details().client_request_token.as_deref()
    }

    /// Get the unique ID of this event.
    #[must_use]
    pub fn event_id(&self) -> &str {
        self.details().event_id.as_str()
    }

    /// Get the logical name of the resource specified in the template.
    #[must_use]
    pub fn logical_resource_id(&self) -> &str {
        self.details().logical_resource_id.as_str()
    }

    /// Get the name of unique identifier associated with the physical instance of the resource.
    ///
    /// This is unset when a physical resource does not exist, e.g. when creation is still in
    /// progress or has failed.
    #[must_use]
    pub fn physical_resource_id(&self) -> Option<&str> {
        self.details().physical_resource_id.as_deref()
    }

    /// Get the success/failure message associated with the resource.
    #[must_use]
    pub fn resource_status_reason(&self) -> Option<&str> {
        self.details().resource_status_reason.as_deref()
    }

    /// Get the type of resource.
    #[must_use]
    pub fn resource_type(&self) -> &str {
        self.details().resource_type.as_str()
    }

    /// Get the unique ID of the instance of the stack.
    #[must_use]
    pub fn stack_id(&self) -> &str {
        self.details().stack_id.as_str()
    }

    /// Get the name associated with the stack.
    #[must_use]
    pub fn stack_name(&self) -> &str {
        self.details().stack_name.as_str()
    }

    /// Get the time the status was updated.
    #[must_use]
    pub fn timestamp(&self) -> &DateTime<Utc> {
        &self.details().timestamp
    }

    /// Indicates whether or not an event is terminal.
    ///
    /// A terminal event is the last one that will occur during the current stack operation. By
    /// definition, the terminal event is an event for the stack itself with a terminal
    /// [`StackStatus`].
    #[must_use]
    pub fn is_terminal(&self) -> bool {
        if let Self::Stack {
            resource_status, ..
        } = self
        {
            resource_status.is_terminal()
        } else {
            false
        }
    }

    pub(crate) fn from_raw(event: rusoto_cloudformation::StackEvent) -> Self {
        let is_stack = event.physical_resource_id.as_deref() == Some(&event.stack_id);
        let resource_status = event
            .resource_status
            .expect("StackEvent without resource_status");
        let details = StackEventDetails {
            client_request_token: event.client_request_token,
            event_id: event.event_id,
            logical_resource_id: event
                .logical_resource_id
                .expect("StackEvent without logical_resource_id"),
            physical_resource_id: event.physical_resource_id,
            resource_status_reason: event.resource_status_reason,
            resource_type: event
                .resource_type
                .expect("StackEvent without resource_type"),
            stack_id: event.stack_id,
            stack_name: event.stack_name,
            timestamp: DateTime::parse_from_rfc3339(&event.timestamp)
                .expect("StackEvent invalid timestamp")
                .into(),
        };
        if is_stack {
            Self::Stack {
                resource_status: resource_status.parse().expect("invalid stack status"),
                details,
            }
        } else {
            Self::Resource {
                resource_status: resource_status.parse().expect("invalid resource status"),
                details,
            }
        }
    }
}

/// Event details from the `DescribeStackEvents` API that are common for stack and resource events.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StackEventDetails {
    /// The token passed to the operation that generated this event.
    ///
    /// All events triggerd by a given stack operation are assigne dthe same client request token,
    /// you can use to track operations. For example, if you execute a `CreateStack` operation with
    /// the token `token1`, then all the `StackEvents` generated by that operation will have
    /// `ClientRequestToken` set as `token1`.
    ///
    /// In the console, stack operations display the client request token on the Events tab. Stack
    /// operations that are initiated from the console use the token format
    /// *Console-StackOperation-ID*, which helps you easily identify the stack operation. For
    /// example, if you create a stack using the console, each stack event would be assigned the
    /// same token in the following format:
    /// `Console-CreateStack-7f59c3cf-00d2-40c7-b2ff-e75db0987002`.
    pub client_request_token: Option<String>,

    /// The unique ID of this event.
    pub event_id: String,

    /// The logical name of the resource specified in the template.
    pub logical_resource_id: String,

    /// The name or unique identifier associated with the physical instance of the resource.
    ///
    /// This is unset when a physical resource does not exist, e.g. when creation is still in
    /// progress or has failed.
    pub physical_resource_id: Option<String>,

    /// Success/failure message associated with the resource.
    pub resource_status_reason: Option<String>,

    /// Type of resource.
    pub resource_type: String,

    /// The unique ID of the instance of the stack.
    pub stack_id: String,

    /// The name associated with the stack.
    pub stack_name: String,

    /// Time the status was updated.
    pub timestamp: DateTime<Utc>,
}

impl StackEventDetails {
    /// Get the token passed to the operation that generated this event.
    ///
    /// All events triggerd by a given stack operation are assigne dthe same client request token,
    /// you can use to track operations. For example, if you execute a `CreateStack` operation with
    /// the token `token1`, then all the `StackEvents` generated by that operation will have
    /// `ClientRequestToken` set as `token1`.
    ///
    /// In the console, stack operations display the client request token on the Events tab. Stack
    /// operations that are initiated from the console use the token format
    /// *Console-StackOperation-ID*, which helps you easily identify the stack operation. For
    /// example, if you create a stack using the console, each stack event would be assigned the
    /// same token in the following format:
    /// `Console-CreateStack-7f59c3cf-00d2-40c7-b2ff-e75db0987002`.
    #[must_use]
    pub fn client_request_token(&self) -> Option<&str> {
        self.client_request_token.as_deref()
    }

    /// Get the unique ID of this event.
    #[must_use]
    pub fn event_id(&self) -> &str {
        self.event_id.as_str()
    }

    /// Get the logical name of the resource specified in the template.
    #[must_use]
    pub fn logical_resource_id(&self) -> &str {
        self.logical_resource_id.as_str()
    }

    /// Get the name of unique identifier associated with the physical instance of the resource.
    ///
    /// This is unset when a physical resource does not exist, e.g. when creation is still in
    /// progress or has failed.
    #[must_use]
    pub fn physical_resource_id(&self) -> Option<&str> {
        self.physical_resource_id.as_deref()
    }

    /// Get the success/failure message associated with the resource.
    #[must_use]
    pub fn resource_status_reason(&self) -> Option<&str> {
        self.resource_status_reason.as_deref()
    }

    /// Get the type of resource.
    #[must_use]
    pub fn resource_type(&self) -> &str {
        self.resource_type.as_str()
    }

    /// Get the unique ID of the instance of the stack.
    #[must_use]
    pub fn stack_id(&self) -> &str {
        self.stack_id.as_str()
    }

    /// Get the name associated with the stack.
    #[must_use]
    pub fn stack_name(&self) -> &str {
        self.stack_name.as_str()
    }

    /// Get the time the status was updated.
    #[must_use]
    pub fn timestamp(&self) -> &DateTime<Utc> {
        &self.timestamp
    }
}