From ff9e37c9b7e3100c7ab1e01da1293f6ea3bfe999 Mon Sep 17 00:00:00 2001
From: mjsterckx <msterckx@amazon.com>
Date: Thu, 13 Oct 2022 23:25:22 +0000
Subject: [PATCH] controller: replaced parse_duration package with custom
 parser

---
 Cargo.lock                                   | 70 --------------
 controller/Cargo.toml                        |  1 -
 controller/src/constants.rs                  | 98 ++++++++++++++++++++
 controller/src/resource_controller/action.rs |  6 +-
 controller/src/test_controller/action.rs     |  4 +-
 5 files changed, 103 insertions(+), 76 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index ef87daf5..b43a0454 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -764,7 +764,6 @@ dependencies = [
  "lazy_static",
  "log",
  "model",
- "parse_duration",
  "schemars",
  "serde",
  "serde_json",
@@ -1725,41 +1724,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
 
-[[package]]
-name = "num"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
-dependencies = [
- "num-bigint",
- "num-complex",
- "num-integer",
- "num-iter",
- "num-rational",
- "num-traits",
-]
-
-[[package]]
-name = "num-bigint"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-complex"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
 [[package]]
 name = "num-integer"
 version = "0.1.45"
@@ -1770,29 +1734,6 @@ dependencies = [
  "num-traits",
 ]
 
-[[package]]
-name = "num-iter"
-version = "0.1.43"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-rational"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
-dependencies = [
- "autocfg",
- "num-bigint",
- "num-integer",
- "num-traits",
-]
-
 [[package]]
 name = "num-traits"
 version = "0.2.15"
@@ -1945,17 +1886,6 @@ dependencies = [
  "windows-sys",
 ]
 
-[[package]]
-name = "parse_duration"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
-dependencies = [
- "lazy_static",
- "num",
- "regex",
-]
-
 [[package]]
 name = "path-absolutize"
 version = "3.0.13"
diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index eb513bf8..c0b74e2f 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -16,7 +16,6 @@ kube-runtime = "0.75"
 lazy_static = "1"
 log = "0.4"
 model = { version = "0.0.2", path = "../model" }
-parse_duration = "2.1"
 schemars = "=0.8.10"
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
diff --git a/controller/src/constants.rs b/controller/src/constants.rs
index 1963211b..b839377f 100644
--- a/controller/src/constants.rs
+++ b/controller/src/constants.rs
@@ -1,6 +1,11 @@
+use crate::error::Result;
+use anyhow::Context;
 use kube_runtime::controller::Action;
+use std::collections::VecDeque;
 use std::time::Duration;
 
+const UNITS: [(char, u64); 3] = [('d', 86400), ('h', 3600), ('m', 60)];
+
 /// Tell the controller to reconcile the object again after some duration.
 pub(crate) fn requeue() -> Action {
     Action::requeue(Duration::from_secs(5))
@@ -15,3 +20,96 @@ pub(crate) fn requeue_slow() -> Action {
 pub(crate) fn no_requeue() -> Action {
     Action::await_change()
 }
+
+/// Parse a Duration string into a Duration object.
+pub(crate) fn parse_duration(input: &str) -> Result<Duration> {
+    let mut secs: u64 = 0;
+    let mut duration_string = input;
+    for unit in UNITS {
+        let mut vec: VecDeque<&str> = duration_string.split(unit.0).collect();
+        if vec.len() > 1 {
+            secs += vec
+                .pop_front()
+                .context("Failed to parse input")?
+                .parse::<u64>()?
+                * unit.1;
+        }
+        duration_string = vec.pop_front().context("Failed to parse input")?;
+    }
+    let mut vec: VecDeque<&str> = duration_string.split('s').collect();
+    let seconds = vec.pop_front().context("Failed to parse input")?;
+    if !seconds.is_empty() {
+        secs += seconds.parse::<u64>()?;
+    }
+    Ok(Duration::from_secs(secs))
+}
+
+#[test]
+fn all_units() {
+    let input = "1d2h3m4s";
+    assert!(
+        parse_duration(input).is_ok()
+            && parse_duration(input).unwrap() == Duration::from_secs(93784)
+    )
+}
+
+#[test]
+fn some_units() {
+    let input = "1d3m4s";
+    assert!(
+        parse_duration(input).is_ok()
+            && parse_duration(input).unwrap() == Duration::from_secs(86584)
+    )
+}
+
+#[test]
+fn only_seconds() {
+    let input = "500s";
+    assert!(
+        parse_duration(input).is_ok() && parse_duration(input).unwrap() == Duration::from_secs(500)
+    )
+}
+
+#[test]
+fn no_seconds() {
+    let input = "1h5m";
+    assert!(
+        parse_duration(input).is_ok()
+            && parse_duration(input).unwrap() == Duration::from_secs(3900)
+    )
+}
+
+#[test]
+fn one_unit() {
+    let input = "10m";
+    assert!(
+        parse_duration(input).is_ok() && parse_duration(input).unwrap() == Duration::from_secs(600)
+    )
+}
+
+#[test]
+fn no_units() {
+    let input = "5123";
+    assert!(
+        parse_duration(input).is_ok()
+            && parse_duration(input).unwrap() == Duration::from_secs(5123)
+    )
+}
+
+#[test]
+fn wrong_order() {
+    let input = "10d5m3h2s";
+    assert!(parse_duration(input).is_err())
+}
+
+#[test]
+fn invalid_unit() {
+    let input = "5y40s";
+    assert!(parse_duration(input).is_err())
+}
+
+#[test]
+fn missing_value() {
+    let input = "5hm4s";
+    assert!(parse_duration(input).is_err())
+}
diff --git a/controller/src/resource_controller/action.rs b/controller/src/resource_controller/action.rs
index b83b18ab..17afa010 100644
--- a/controller/src/resource_controller/action.rs
+++ b/controller/src/resource_controller/action.rs
@@ -1,3 +1,4 @@
+use crate::constants::parse_duration;
 use crate::error::Result;
 use crate::job::{JobState, TEST_START_TIME_LIMIT};
 use crate::resource_controller::context::ResourceInterface;
@@ -7,7 +8,6 @@ use log::{debug, trace};
 use model::clients::{AllowNotFound, CrdClient, TestClient};
 use model::constants::{FINALIZER_CREATION_JOB, FINALIZER_MAIN, FINALIZER_RESOURCE};
 use model::{CrdExt, DestructionPolicy, ResourceAction, TaskState, TestUserState};
-use parse_duration::parse;
 
 /// The action that the controller needs to take in order to reconcile the [`Resource`].
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -155,7 +155,7 @@ async fn creation_not_done_action(
                     .agent
                     .timeout
                     .as_ref()
-                    .map(|timeout| parse(timeout).map(|timeout| std_duration > timeout))
+                    .map(|timeout| parse_duration(timeout).map(|timeout| std_duration > timeout))
                     .unwrap_or(Ok(false))
                     .unwrap_or(false)
                 {
@@ -299,7 +299,7 @@ async fn destruction_not_done_action(
                     .agent
                     .timeout
                     .as_ref()
-                    .map(|timeout| parse(timeout).map(|timeout| std_duration > timeout))
+                    .map(|timeout| parse_duration(timeout).map(|timeout| std_duration > timeout))
                     .unwrap_or(Ok(false))
                     .unwrap_or(false)
                 {
diff --git a/controller/src/test_controller/action.rs b/controller/src/test_controller/action.rs
index 7bd28946..4bb7ad3f 100644
--- a/controller/src/test_controller/action.rs
+++ b/controller/src/test_controller/action.rs
@@ -1,3 +1,4 @@
+use crate::constants::parse_duration;
 use crate::error::Result;
 use crate::job::{JobState, TEST_START_TIME_LIMIT};
 use crate::test_controller::context::TestInterface;
@@ -7,7 +8,6 @@ use log::trace;
 use model::clients::{CrdClient, HttpStatusCode, StatusCode};
 use model::constants::{FINALIZER_MAIN, FINALIZER_TEST_JOB, NAMESPACE};
 use model::{CrdExt, Outcome, Resource, ResourceAction, TaskState};
-use parse_duration::parse;
 use std::fmt::{Display, Formatter};
 
 /// The action that the controller needs to take in order to reconcile the `Test`.
@@ -219,7 +219,7 @@ async fn task_not_done_action(t: &TestInterface, is_task_state_running: bool) ->
                     .agent
                     .timeout
                     .as_ref()
-                    .map(|timeout| parse(timeout).map(|timeout| std_duration > timeout))
+                    .map(|timeout| parse_duration(timeout).map(|timeout| std_duration > timeout))
                     .unwrap_or(Ok(false))
                     .unwrap_or(false)
                 {