pub mod manifest;
pub mod validate;
mod vendor;
use amalgam_codegen::nickel::NickelCodegen;
use amalgam_codegen::Codegen;
use amalgam_parser::k8s_types::K8sTypesFetcher;
use anyhow::Result;
use std::fs;
use std::path::Path;
use tracing::info;
fn is_core_k8s_type(name: &str) -> bool {
matches!(
name,
"ObjectMeta"
| "ListMeta"
| "LabelSelector"
| "Time"
| "MicroTime"
| "Status"
| "StatusDetails"
| "StatusCause"
| "FieldsV1"
| "ManagedFieldsEntry"
| "OwnerReference"
| "Preconditions"
| "DeleteOptions"
| "ListOptions"
| "GetOptions"
| "WatchEvent"
| "Condition"
| "TypeMeta"
| "APIResource"
| "APIResourceList"
| "APIGroup"
| "APIGroupList"
| "APIVersions"
| "GroupVersionForDiscovery"
)
}
fn is_unversioned_k8s_type(name: &str) -> bool {
matches!(
name,
"RawExtension" | "IntOrString" )
}
fn collect_type_references(
ty: &amalgam_core::types::Type,
refs: &mut std::collections::HashSet<String>,
) {
use amalgam_core::types::Type;
match ty {
Type::Reference(name) => {
refs.insert(name.clone());
}
Type::Array(inner) => collect_type_references(inner, refs),
Type::Optional(inner) => collect_type_references(inner, refs),
Type::Map { value, .. } => collect_type_references(value, refs),
Type::Record { fields, .. } => {
for field in fields.values() {
collect_type_references(&field.ty, refs);
}
}
Type::Union(types) => {
for t in types {
collect_type_references(t, refs);
}
}
Type::TaggedUnion { variants, .. } => {
for t in variants.values() {
collect_type_references(t, refs);
}
}
Type::Contract { base, .. } => collect_type_references(base, refs),
_ => {}
}
}
fn apply_type_replacements(
ty: &mut amalgam_core::types::Type,
replacements: &std::collections::HashMap<String, String>,
) {
use amalgam_core::types::Type;
match ty {
Type::Reference(name) => {
if let Some(replacement) = replacements.get(name) {
*name = replacement.clone();
}
}
Type::Array(inner) => apply_type_replacements(inner, replacements),
Type::Optional(inner) => apply_type_replacements(inner, replacements),
Type::Map { value, .. } => apply_type_replacements(value, replacements),
Type::Record { fields, .. } => {
for field in fields.values_mut() {
apply_type_replacements(&mut field.ty, replacements);
}
}
Type::Union(types) => {
for t in types {
apply_type_replacements(t, replacements);
}
}
Type::TaggedUnion { variants, .. } => {
for t in variants.values_mut() {
apply_type_replacements(t, replacements);
}
}
Type::Contract { base, .. } => apply_type_replacements(base, replacements),
_ => {}
}
}
pub async fn handle_k8s_core_import(
version: &str,
output_dir: &Path,
nickel_package: bool,
) -> Result<()> {
info!("Fetching Kubernetes {} core types...", version);
let fetcher = K8sTypesFetcher::new();
let openapi = fetcher.fetch_k8s_openapi(version).await?;
let types = fetcher.extract_core_types(&openapi)?;
let total_types = types.len();
info!("Extracted {} core types", total_types);
let mut types_by_version: std::collections::HashMap<
String,
Vec<(
amalgam_parser::imports::TypeReference,
amalgam_core::ir::TypeDefinition,
)>,
> = std::collections::HashMap::new();
for (type_ref, type_def) in types {
types_by_version
.entry(type_ref.version.clone())
.or_default()
.push((type_ref, type_def));
}
for (version, version_types) in &types_by_version {
let version_dir = output_dir.join(version);
fs::create_dir_all(&version_dir)?;
let mut mod_imports = Vec::new();
for (type_ref, type_def) in version_types {
let mut imports = Vec::new();
let mut type_replacements = std::collections::HashMap::new();
let mut referenced_types = std::collections::HashSet::new();
collect_type_references(&type_def.ty, &mut referenced_types);
for referenced in &referenced_types {
if !referenced.contains('.') && referenced != &type_ref.kind {
if version_types.iter().any(|(tr, _)| tr.kind == *referenced) {
let alias = referenced.to_lowercase();
imports.push(amalgam_core::ir::Import {
path: format!("./{}.ncl", alias),
alias: Some(alias.clone()),
items: vec![referenced.clone()],
});
type_replacements
.insert(referenced.clone(), format!("{}.{}", alias, referenced));
} else if is_core_k8s_type(referenced) {
let source_version = "v1";
if version != source_version {
let alias = referenced; imports.push(amalgam_core::ir::Import {
path: format!(
"../{}/{}.ncl",
source_version,
referenced.to_lowercase()
),
alias: Some(alias.to_string()),
items: vec![],
});
}
} else if is_unversioned_k8s_type(referenced) {
let source_version = "v0";
if version != source_version {
let alias = referenced; imports.push(amalgam_core::ir::Import {
path: format!(
"../{}/{}.ncl",
source_version,
referenced.to_lowercase()
),
alias: Some(alias.to_string()),
items: vec![],
});
}
}
}
}
let mut updated_type_def = type_def.clone();
apply_type_replacements(&mut updated_type_def.ty, &type_replacements);
let module = amalgam_core::ir::Module {
name: format!(
"k8s.io.{}.{}",
type_ref.version,
type_ref.kind.to_lowercase()
),
imports,
types: vec![updated_type_def],
constants: vec![],
metadata: Default::default(),
};
let mut ir = amalgam_core::IR::new();
ir.add_module(module);
let mut codegen = NickelCodegen::new();
let code = codegen.generate(&ir)?;
let filename = format!("{}.ncl", type_ref.kind.to_lowercase());
let file_path = version_dir.join(&filename);
fs::write(&file_path, code)?;
info!("Generated {:?}", file_path);
mod_imports.push(format!(
" {} = (import \"./{}\").{},",
type_ref.kind, filename, type_ref.kind
));
}
let mod_content = format!(
"# Kubernetes core {} types\n{{\n{}\n}}\n",
version,
mod_imports.join("\n")
);
fs::write(version_dir.join("mod.ncl"), mod_content)?;
}
let mut version_imports = Vec::new();
for version in types_by_version.keys() {
version_imports.push(format!(" {} = import \"./{}/mod.ncl\",", version, version));
}
let root_mod_content = format!(
"# Kubernetes core types\n{{\n{}\n}}\n",
version_imports.join("\n")
);
fs::write(output_dir.join("mod.ncl"), root_mod_content)?;
if nickel_package {
info!("Generating Nickel package manifest (experimental)");
use amalgam_codegen::nickel_package::{NickelPackageConfig, NickelPackageGenerator};
let config = NickelPackageConfig {
name: "k8s-io".to_string(),
version: "0.1.0".to_string(),
minimal_nickel_version: "1.9.0".to_string(),
description: format!("Kubernetes {} core type definitions for Nickel", version),
authors: vec!["amalgam".to_string()],
license: "Apache-2.0".to_string(),
keywords: vec![
"kubernetes".to_string(),
"k8s".to_string(),
"types".to_string(),
],
};
let generator = NickelPackageGenerator::new(config);
let modules: Vec<amalgam_core::ir::Module> = types_by_version
.keys()
.map(|ver| amalgam_core::ir::Module {
name: ver.clone(),
imports: Vec::new(),
types: Vec::new(),
constants: Vec::new(),
metadata: Default::default(),
})
.collect();
let manifest = generator
.generate_manifest(&modules, std::collections::HashMap::new())
.unwrap_or_else(|e| format!("# Error generating manifest: {}\n", e));
fs::write(output_dir.join("Nickel-pkg.ncl"), manifest)?;
info!("✓ Generated Nickel-pkg.ncl");
}
info!(
"Successfully generated {} k8s core types in {:?}",
total_types, output_dir
);
if nickel_package {
info!(" with Nickel package manifest");
}
Ok(())
}