Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 46 additions & 30 deletions crates/alien-core/src/access_request_crd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,34 @@
//!
//! The operator manifest (`alien-helm`) and the operator runtime
//! (`alien-access-request-crd-loop`) BOTH derive the CRD's group/kind/plural
//! from the deployment's branding domain here, so the resource the manifest
//! from the deployment's brand name here, so the resource the manifest
//! registers is exactly the one the operator creates and watches — they can't
//! drift.
//!
//! For a vendor whose branded domain is `acme.dev`, the access-request CRD is:
//! Kubernetes requires the API group to be *shaped* like a DNS subdomain, but
//! never resolves it — so the brand isn't a domain the vendor owns, just a
//! stable, customer-facing identity (e.g. the project name) slugified into
//! that shape.
//!
//! For a vendor branded `acme`, the access-request CRD is:
//!
//! ```text
//! group: accessrequests.acme.dev
//! group: accessrequests.acme
//! kind: AcmeAccessRequest
//! plural: acmeaccessrequests
//! short: acmear
//! ```
//!
//! When no branding domain is set it falls back to the Alien defaults
//! (`accessrequests.alien.dev` / `AlienAccessRequest`).
//! When no brand is set it falls back to the Alien defaults
//! (`accessrequests.alien` / `AlienAccessRequest`).

/// The default (unbranded) DNS domain the access-request CRD lives under.
pub const DEFAULT_LABEL_DOMAIN: &str = "alien.dev";
/// The default (unbranded) slug the access-request CRD lives under.
pub const DEFAULT_BRAND: &str = "alien";

/// Derived, white-labeled names for the access-request custom resource.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessRequestCrdNames {
/// API group, e.g. `accessrequests.acme.dev`.
/// API group, e.g. `accessrequests.acme`.
pub group: String,
/// Resource kind, e.g. `AcmeAccessRequest`.
pub kind: String,
Expand All @@ -34,30 +39,30 @@ pub struct AccessRequestCrdNames {
pub singular: String,
/// Short name, e.g. `acmear`.
pub short_name: String,
/// The API version, e.g. `accessrequests.acme.dev/v1alpha1`.
/// The API version, e.g. `accessrequests.acme/v1alpha1`.
pub api_version: String,
/// The CRD object's `metadata.name`, e.g.
/// `acmeaccessrequests.accessrequests.acme.dev`.
/// `acmeaccessrequests.accessrequests.acme`.
pub crd_name: String,
}

/// The CRD version served (single alpha version for now).
pub const ACCESS_REQUEST_CRD_VERSION: &str = "v1alpha1";

/// Derive the access-request-CRD names from a branding domain (e.g.
/// `Some("acme.dev")`). `None`/empty → the Alien defaults.
/// Derive the access-request-CRD names from a brand name (e.g.
/// `Some("acme")`, or `Some("Acme Corp")`). `None`/empty → the Alien defaults.
///
/// The brand slug is the domain's first DNS label lowercased and stripped of
/// non-alphanumerics (`acme.dev` → `acme`, `my-startup.io` → `mystartup`). The
/// The brand slug is the input's first DNS label lowercased and stripped of
/// non-alphanumerics (`Acme Corp` → `acmecorp`, `acme.dev` → `acme`). The
/// kind capitalizes it (`Acme` → `AcmeAccessRequest`).
pub fn access_request_crd_names(label_domain: Option<&str>) -> AccessRequestCrdNames {
let domain = label_domain
pub fn access_request_crd_names(brand_name: Option<&str>) -> AccessRequestCrdNames {
let name = brand_name
.map(str::trim)
.filter(|d| !d.is_empty())
.unwrap_or(DEFAULT_LABEL_DOMAIN);
.filter(|n| !n.is_empty())
.unwrap_or(DEFAULT_BRAND);

let brand = brand_slug(domain);
let group = format!("accessrequests.{domain}");
let brand = brand_slug(name);
Comment thread
ab-alien-dev marked this conversation as resolved.
let group = format!("accessrequests.{brand}");
let plural = format!("{brand}accessrequests");
let singular = format!("{brand}accessrequest");
let short_name = format!("{brand}ar");
Expand All @@ -74,9 +79,11 @@ pub fn access_request_crd_names(label_domain: Option<&str>) -> AccessRequestCrdN
}
}

/// The lowercase alphanumeric brand slug from a domain's first label.
fn brand_slug(domain: &str) -> String {
let first_label = domain.split('.').next().unwrap_or(domain);
/// The lowercase alphanumeric brand slug from a name's first dot-separated
/// label (so a real domain's first label still works as input), with any
/// remaining non-alphanumerics (spaces, punctuation) stripped out.
fn brand_slug(name: &str) -> String {
let first_label = name.split('.').next().unwrap_or(name);
let slug: String = first_label
.chars()
.filter(|c| c.is_ascii_alphanumeric())
Expand Down Expand Up @@ -105,23 +112,32 @@ mod tests {
#[test]
fn default_is_alien() {
let n = access_request_crd_names(None);
assert_eq!(n.group, "accessrequests.alien.dev");
assert_eq!(n.group, "accessrequests.alien");
assert_eq!(n.kind, "AlienAccessRequest");
assert_eq!(n.plural, "alienaccessrequests");
assert_eq!(n.short_name, "alienar");
assert_eq!(n.crd_name, "alienaccessrequests.accessrequests.alien.dev");
assert_eq!(n.api_version, "accessrequests.alien.dev/v1alpha1");
assert_eq!(n.crd_name, "alienaccessrequests.accessrequests.alien");
assert_eq!(n.api_version, "accessrequests.alien/v1alpha1");
}

#[test]
fn brands_from_domain() {
let n = access_request_crd_names(Some("acme.dev"));
assert_eq!(n.group, "accessrequests.acme.dev");
assert_eq!(n.group, "accessrequests.acme");
assert_eq!(n.kind, "AcmeAccessRequest");
assert_eq!(n.plural, "acmeaccessrequests");
assert_eq!(n.singular, "acmeaccessrequest");
assert_eq!(n.short_name, "acmear");
assert_eq!(n.crd_name, "acmeaccessrequests.accessrequests.acme.dev");
assert_eq!(n.crd_name, "acmeaccessrequests.accessrequests.acme");
}

#[test]
fn brands_from_plain_name() {
let n = access_request_crd_names(Some("My Cool App"));
assert_eq!(n.group, "accessrequests.mycoolapp");
assert_eq!(n.kind, "MycoolappAccessRequest");
assert_eq!(n.plural, "mycoolappaccessrequests");
assert_eq!(n.short_name, "mycoolappar");
}

#[test]
Expand All @@ -130,13 +146,13 @@ mod tests {
let n = access_request_crd_names(Some("globex.dev"));
assert_eq!(n.plural, "globexaccessrequests");
assert_eq!(n.kind, "GlobexAccessRequest");
assert_eq!(n.group, "accessrequests.globex.dev");
assert_eq!(n.group, "accessrequests.globex");
}

#[test]
fn strips_non_alphanumerics_from_slug() {
let n = access_request_crd_names(Some("my-startup.io"));
assert_eq!(n.group, "accessrequests.my-startup.io");
assert_eq!(n.group, "accessrequests.mystartup");
assert_eq!(n.kind, "MystartupAccessRequest");
assert_eq!(n.plural, "mystartupaccessrequests");
}
Expand Down
25 changes: 13 additions & 12 deletions crates/alien-helm/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,10 @@ pub struct OperatorManifestOptions<'a> {
/// `RawManifest`; ignored for `HelmTemplate`, which uses `.Release.Namespace`.
/// In `Namespace` scope this is also the namespace observed.
pub install_namespace: Option<&'a str>,
/// The vendor's branded DNS domain (e.g. `acme.dev`), used to white-label
/// the access-request CRD (group/kind/plural). `None` → the Alien defaults.
/// The vendor's brand name (e.g. `acme`, or a real owned domain like
/// `acme.dev`), used to white-label the access-request CRD
/// (group/kind/plural). It's slugified, never resolved as DNS — any
/// stable customer-facing identity works. `None` → the Alien defaults.
/// Same value the operator carries at runtime, so both agree on the CRD.
pub label_domain: Option<&'a str>,
pub scope: OperatorScope,
Expand Down Expand Up @@ -4277,7 +4279,7 @@ mod tests {
// The access-request CRD is the operator's own control resource: it
// materializes access requests and records the approval window in
// status, but never deletes them.
if api_groups == vec!["accessrequests.alien.dev"] {
if api_groups == vec!["accessrequests.alien"] {
assert!(
verbs.iter().all(|v| matches!(
*v,
Expand Down Expand Up @@ -4358,9 +4360,11 @@ mod tests {

#[test]
fn access_request_crd_is_white_labeled_from_the_brand_domain() {
// A vendor whose branded domain is acme.dev gets AcmeAccessRequest, not
// A vendor branded acme.dev gets AcmeAccessRequest, not
// AlienAccessRequest — the CRD, RBAC, and (elsewhere) the operator
// runtime all derive from the same domain.
// runtime all derive from the same brand slug. The brand's DNS shape
// is never required — it's slugified into the CRD group, never
// resolved.
let manifest = generate_operator_manifest(OperatorManifestOptions {
manager_url: "https://manager.example.com",
group_token: "ax_dg_test",
Expand All @@ -4386,11 +4390,11 @@ mod tests {
.expect("manifest should include the access-request CRD");
assert_eq!(
yaml_path(&crd, &["metadata", "name"]).and_then(YamlValue::as_str),
Some("acmeaccessrequests.accessrequests.acme.dev")
Some("acmeaccessrequests.accessrequests.acme")
);
assert_eq!(
yaml_path(&crd, &["spec", "group"]).and_then(YamlValue::as_str),
Some("accessrequests.acme.dev")
Some("accessrequests.acme")
);
assert_eq!(
yaml_path(&crd, &["spec", "names", "kind"]).and_then(YamlValue::as_str),
Expand All @@ -4407,7 +4411,7 @@ mod tests {
"no alien-named resource in a branded build"
);
assert!(
!text.contains("accessrequests.alien.dev"),
!text.contains("accessrequests.alien"),
"no alien group in a branded build"
);

Expand All @@ -4421,10 +4425,7 @@ mod tests {
.any(|r| {
r.get("apiGroups")
.and_then(YamlValue::as_sequence)
.map(|g| {
g.iter()
.any(|x| x.as_str() == Some("accessrequests.acme.dev"))
})
.map(|g| g.iter().any(|x| x.as_str() == Some("accessrequests.acme")))
.unwrap_or(false)
});
assert!(
Expand Down
16 changes: 11 additions & 5 deletions crates/alien-operator/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,17 @@ async fn run(
.maybe_label_selector(args.operator_label_selector)
.observe_all_namespaces(args.operator_observe_all_namespaces)
.maybe_app_version(args.operator_release_version)
.maybe_label_domain(
embedded_config
.as_ref()
.and_then(|config| config.label_domain.clone()),
)
.maybe_label_domain(embedded_config.as_ref().and_then(|config| {
// `brand` is the already-slugged identity `access_request_crd_names`
// expects (see alien-core::access_request_crd) — packages-builder
// computes it once and embeds both fields, but `label_domain` is
// the raw, pre-slug config value (e.g. a placeholder like
// "acme.local"), which can disagree with what the manifest
// generator derived the CRD/RBAC from. Prefer `brand`; fall back
// to `label_domain` only for older embedded binaries that
// predate the `brand` field.
config.brand.clone().or_else(|| config.label_domain.clone())
}))
.maybe_collector_token(collector_token)
.maybe_public_endpoints(public_endpoints)
.stack_settings(stack_settings)
Expand Down
Loading