From 5266e9dcad18513ebffce4ecee8085110c484719 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 13 Jul 2022 16:48:32 -0400 Subject: [PATCH 01/11] Ensure names don't overlap with UUIDs --- common/src/api/external/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index f6c97d690fa..a9252553b7d 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -169,6 +169,10 @@ impl TryFrom for Name { return Err(String::from("name cannot end with \"-\"")); } + if Uuid::parse_str(&value).is_ok() { + return Err(String::from("name cannot be a UUID to avoid ambiguity with ids")); + } + Ok(Name(value)) } } @@ -1976,6 +1980,10 @@ mod test { "name contains invalid character: \"\u{00e9}\" (allowed \ characters are lowercase ASCII, digits, and \"-\")", ), + ( + "a7e55044-10b1-426f-9247-bb680e5fe0c8", + "name cannot be a UUID to avoid ambiguity with ids" + ) ]; for (input, expected_message) in error_cases { @@ -1985,7 +1993,7 @@ mod test { // Success cases let valid_names: Vec<&str> = - vec!["abc", "abc-123", "a123", &long_name[0..63]]; + vec!["abc", "abc-123", "a123", "ok-a7e55044-10b1-426f-9247-bb680e5fe0c8", &long_name[0..63]]; for name in valid_names { eprintln!("check name \"{}\" (should be valid)", name); From e42f6faebc548d207912336127c7a5397fdeb5e9 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Wed, 13 Jul 2022 17:10:45 -0400 Subject: [PATCH 02/11] Fix formatting --- common/src/api/external/mod.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index a9252553b7d..b8bd1dcc3f6 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -170,7 +170,9 @@ impl TryFrom for Name { } if Uuid::parse_str(&value).is_ok() { - return Err(String::from("name cannot be a UUID to avoid ambiguity with ids")); + return Err(String::from( + "name cannot be a UUID to avoid ambiguity with ids", + )); } Ok(Name(value)) @@ -1982,8 +1984,8 @@ mod test { ), ( "a7e55044-10b1-426f-9247-bb680e5fe0c8", - "name cannot be a UUID to avoid ambiguity with ids" - ) + "name cannot be a UUID to avoid ambiguity with ids", + ), ]; for (input, expected_message) in error_cases { @@ -1992,8 +1994,13 @@ mod test { } // Success cases - let valid_names: Vec<&str> = - vec!["abc", "abc-123", "a123", "ok-a7e55044-10b1-426f-9247-bb680e5fe0c8", &long_name[0..63]]; + let valid_names: Vec<&str> = vec![ + "abc", + "abc-123", + "a123", + "ok-a7e55044-10b1-426f-9247-bb680e5fe0c8", + &long_name[0..63], + ]; for name in valid_names { eprintln!("check name \"{}\" (should be valid)", name); From 8174c319b6d2a6881e3c88e529ed8afa44e47a16 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 15:54:03 -0400 Subject: [PATCH 03/11] Note Name cannot be a UUID --- common/src/api/external/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index fd0e5e7da7b..787f8a4a026 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -222,7 +222,8 @@ impl JsonSchema for Name { description: Some( "Names must begin with a lower case ASCII letter, be \ composed exclusively of lowercase ASCII, uppercase \ - ASCII, numbers, and '-', and may not end with a '-'." + ASCII, numbers, and '-', and may not end with a '-'.\ + Names also cannot be a UUID though they may contain a UUID." .to_string(), ), ..Default::default() From 89956ed960b0d7732efa94dc59c499c3355ab685 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 16:23:42 -0400 Subject: [PATCH 04/11] Update Name regex --- common/src/api/external/mod.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 787f8a4a026..13e3bd53750 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -223,7 +223,7 @@ impl JsonSchema for Name { "Names must begin with a lower case ASCII letter, be \ composed exclusively of lowercase ASCII, uppercase \ ASCII, numbers, and '-', and may not end with a '-'.\ - Names also cannot be a UUID though they may contain a UUID." + Names cannot be a UUID though they may contain a UUID." .to_string(), ), ..Default::default() @@ -232,7 +232,16 @@ impl JsonSchema for Name { string: Some(Box::new(schemars::schema::StringValidation { max_length: Some(63), min_length: None, - pattern: Some("^[a-z](|[a-zA-Z0-9-]*[a-zA-Z0-9])$".to_string()), + pattern: Some( + concat!( + r#"^"#, + // Cannot match a UUID + r#"(?!\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b$)"#, + r#"^[a-z][a-z0-9\-]*[a-zA-Z0-9]"#, + r#"$"#, + ) + .to_string(), + ) })), ..Default::default() } From 60aae26cc732011ccca73ce1a508aed97dcabe74 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 16:29:46 -0400 Subject: [PATCH 05/11] Add extra test for a UUID Name w/ a suffix --- common/src/api/external/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 13e3bd53750..b59a9f6a921 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2009,6 +2009,7 @@ mod test { "abc-123", "a123", "ok-a7e55044-10b1-426f-9247-bb680e5fe0c8", + "a7e55044-10b1-426f-9247-bb680e5fe0c8-ok", &long_name[0..63], ]; From 9a6231f05dc267c6d0d5fe5e2cb2a54b172ab628 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 17:47:39 -0400 Subject: [PATCH 06/11] Update regex to match upper case chars --- common/src/api/external/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index b59a9f6a921..6ec75ad4266 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -236,7 +236,7 @@ impl JsonSchema for Name { concat!( r#"^"#, // Cannot match a UUID - r#"(?!\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b$)"#, + r#"(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)"#, r#"^[a-z][a-z0-9\-]*[a-zA-Z0-9]"#, r#"$"#, ) From a28413809f482a794d3c49559e02bc068f3806b1 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 18:34:17 -0400 Subject: [PATCH 07/11] Update api spec --- openapi/nexus.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/nexus.json b/openapi/nexus.json index 61e8739144a..29545b00362 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8290,9 +8290,9 @@ }, "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.Names cannot be a UUID though they may contain a UUID.", "type": "string", - "pattern": "^[a-z](|[a-zA-Z0-9-]*[a-zA-Z0-9])$", + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9\\-]*[a-zA-Z0-9]$", "maxLength": 63 }, "NetworkInterface": { From 83f7ffebd2f2b86abfde698e2e197ed18b6e9818 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 18:35:26 -0400 Subject: [PATCH 08/11] Update common/src/api/external/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Karen Cárcamo --- common/src/api/external/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 6ec75ad4266..31439182afb 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -171,7 +171,7 @@ impl TryFrom for Name { if Uuid::parse_str(&value).is_ok() { return Err(String::from( - "name cannot be a UUID to avoid ambiguity with ids", + "name cannot be a UUID to avoid ambiguity with IDs", )); } From 34bd0eedcc74db528c095385c3dd0d5b88e08431 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 14 Jul 2022 22:19:23 -0400 Subject: [PATCH 09/11] Last tweak to the regex to remove unnecessary slash --- common/src/api/external/mod.rs | 2 +- openapi/nexus.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 31439182afb..f563dc7982c 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -237,7 +237,7 @@ impl JsonSchema for Name { r#"^"#, // Cannot match a UUID r#"(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)"#, - r#"^[a-z][a-z0-9\-]*[a-zA-Z0-9]"#, + r#"^[a-z][a-z0-9-]*[a-zA-Z0-9]"#, r#"$"#, ) .to_string(), diff --git a/openapi/nexus.json b/openapi/nexus.json index 29545b00362..ab94811be9e 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8292,7 +8292,7 @@ "title": "A name unique within the parent collection", "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.Names cannot be a UUID though they may contain a UUID.", "type": "string", - "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9\\-]*[a-zA-Z0-9]$", + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$", "maxLength": 63 }, "NetworkInterface": { From 05065159265c540d8efc070433dd066eb2f36edd Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Fri, 15 Jul 2022 01:33:25 -0400 Subject: [PATCH 10/11] Fix tests, add space in docs --- common/src/api/external/mod.rs | 2 +- common/tests/output/pagination-schema.txt | 8 ++++---- openapi/nexus.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index f563dc7982c..f97aebb54f7 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -222,7 +222,7 @@ impl JsonSchema for Name { description: Some( "Names must begin with a lower case ASCII letter, be \ composed exclusively of lowercase ASCII, uppercase \ - ASCII, numbers, and '-', and may not end with a '-'.\ + ASCII, numbers, and '-', and may not end with a '-'. \ Names cannot be a UUID though they may contain a UUID." .to_string(), ), diff --git a/common/tests/output/pagination-schema.txt b/common/tests/output/pagination-schema.txt index 0e94e0b1e85..f21aa3aa705 100644 --- a/common/tests/output/pagination-schema.txt +++ b/common/tests/output/pagination-schema.txt @@ -108,10 +108,10 @@ schema for pagination parameters: page selector, scan by name only "definitions": { "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", "type": "string", "maxLength": 63, - "pattern": "^[a-z](|[a-zA-Z0-9-]*[a-zA-Z0-9])$" + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$" }, "NameSortMode": { "description": "Supported set of sort modes for scanning by name only\n\nCurrently, we only support scanning in ascending order.", @@ -186,10 +186,10 @@ schema for pagination parameters: page selector, scan by name or id "definitions": { "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", "type": "string", "maxLength": 63, - "pattern": "^[a-z](|[a-zA-Z0-9-]*[a-zA-Z0-9])$" + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$" }, "NameOrIdMarker": { "oneOf": [ diff --git a/openapi/nexus.json b/openapi/nexus.json index ab94811be9e..00a565dcaf5 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -8290,7 +8290,7 @@ }, "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.Names cannot be a UUID though they may contain a UUID.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", "type": "string", "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$", "maxLength": 63 From 707448a18ccf7def97ab387effa99a60fc4dc019 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Fri, 15 Jul 2022 13:36:55 -0400 Subject: [PATCH 11/11] Out of date test snapshots, corret a test using a UUID as name --- common/src/api/external/mod.rs | 2 +- nexus/tests/integration_tests/basic.rs | 6 +++--- openapi/sled-agent.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index f97aebb54f7..5a2ada97e64 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -1994,7 +1994,7 @@ mod test { ), ( "a7e55044-10b1-426f-9247-bb680e5fe0c8", - "name cannot be a UUID to avoid ambiguity with ids", + "name cannot be a UUID to avoid ambiguity with IDs", ), ]; diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index 3f59e9afd6f..b1ea3bcca5c 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -476,10 +476,10 @@ async fn test_projects_list(cptestctx: &ControlPlaneTestContext) { for _ in 0..projects_total { // We'll use uuids for the names to make sure that works, and that we // can paginate through by _name_ even though the names happen to be - // uuids. Names have to start with a letter, though, so we've got to - // make sure our uuid has one. + // uuids. Names have to start with a letter and can't exactly match + // a uuid though, so we'll use a prefix. let mut name = Uuid::new_v4().to_string(); - name.replace_range(0..1, "a"); + name.insert_str(0, "project-"); let project = create_project(&client, org_name, &name).await; projects_created.push(project.identity); } diff --git a/openapi/sled-agent.json b/openapi/sled-agent.json index 09a9d1daf9a..85907465472 100644 --- a/openapi/sled-agent.json +++ b/openapi/sled-agent.json @@ -1053,9 +1053,9 @@ }, "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", "type": "string", - "pattern": "^[a-z](|[a-zA-Z0-9-]*[a-zA-Z0-9])$", + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$", "maxLength": 63 }, "NetworkInterface": {