diff --git a/.ai/context.md b/.ai/context.md deleted file mode 100644 index 223e6e6..0000000 --- a/.ai/context.md +++ /dev/null @@ -1,29 +0,0 @@ -# Context for AI Assistance - -## Who I Am -A senior engineer with a focus on building software that’s simple, maintainable, and human-readable. I’ve seen enough convoluted systems to know that complexity often masks mediocrity. As E.F. Schumacher (or perhaps Einstein) said: *"Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius — and a lot of courage — to move in the opposite direction."* I live by this. - -## My Preferences -- **Simplicity**: Favor straightforward solutions over clever ones. If it’s hard to explain to a junior engineer, it’s probably too complex. -- **Readability**: Code and documentation should be self-explanatory. Prioritize clear variable names, minimal abstraction layers, and plain English. -- **Practicality**: Solve the problem at hand, not hypothetical future ones. Avoid over-engineering. -- **Rust**: My language of choice for its balance of performance and safety. Keep idioms Rusty but accessible. - -## Project Context -This is `hops-cli`, a Rust CLI tool for Harmony Operations. - -## How to Help Me -- **Keep It Simple**: Propose minimal changes to fix issues. Avoid adding layers unless absolutely necessary. -- **Explain Clearly**: Use plain language, like you’re teaching a curious colleague. No jargon for jargon’s sake. -- **Debugging**: Suggest targeted logging or checks to isolate problems. I’ll run them and share output. -- **Code Style**: Match the existing style—flat structure, explicit error handling, and concise functions. -- **Solutions**: If the API’s the bottleneck, suggest practical alternatives (e.g., pagination, different endpoints) without turning this into a monolith. - -## Example Prompt Response -**Prompt**: "How do I get more events?" -**Good Response**: "The current API endpoint limits us to 100 events per page. Add a loop to check the `Link` header in the response and fetch additional pages if they exist. Here’s a simple change to `main.rs` that logs the header—run it and share the output, then we can add pagination if needed." - -## How to respond when giving me code -1. First tell me all of the files you are going to give me and their directory structure, ask to proceed (I'll say next or something like that). If it's one file, you can just give me the one file instead. -2. If there are more than one, send me one file at a time, ask to proceed until they are all sent. This gives me a chance to review each file and ask for changes. These changes may affect other files, and as such may require regenerating some. Keep track of these and adjust accordingly so I have all the correct files. - diff --git a/README.md b/README.md index 1d97c98..50ccb80 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ # hops-cli -`hops-cli` is a Rust CLI for running a local Crossplane development environment on Colima. +`hops-cli` is a Rust CLI for Crossplane development and XR lifecycle workflows. ## Overview -This tool manages a local Kubernetes stack and package workflow for Crossplane: +This tool supports three related workflows: -- Installs and manages Colima -- Starts a local k8s cluster with Crossplane installed via Helm -- Installs the Kubernetes and Helm Crossplane providers -- Deploys an in-cluster OCI registry (`crossplane-system/registry`) -- Builds and publishes Crossplane configuration packages from an XRD project +- Local cluster setup on Colima +- Configuration package install/uninstall against the connected cluster +- XR observe/manage/adopt/orphan workflows for existing infrastructure + +For local development, it can also: + +- Install and manage Colima +- Start a local k8s cluster with Crossplane installed via Helm +- Install the Kubernetes and Helm Crossplane providers +- Deploy an in-cluster OCI registry (`crossplane-system/registry`) +- Build and publish Crossplane configuration packages from an XRD project ## Installation @@ -70,6 +76,8 @@ cargo build --features vendored hops --help hops local --help hops config --help +hops validate --help +hops xr --help ``` From source without installing: @@ -78,6 +86,8 @@ From source without installing: cargo run -- --help cargo run -- local --help cargo run -- config --help +cargo run -- validate --help +cargo run -- xr --help ``` ## Quick Start @@ -96,22 +106,34 @@ cargo run -- local aws --profile cargo run -- local github --owner # 5) Build and load a Crossplane configuration package from an XRD project -cargo run -- local config --path /path/to/project +cargo run -- config install --path /path/to/project # 6) Build from a GitHub repo (cached clone + build + push to local registry) -cargo run -- local config --repo hops-ops/helm-certmanager +cargo run -- config install --repo hops-ops/helm-certmanager # 7) Force reload from source (deletes existing ConfigurationRevision(s) first) -cargo run -- local config --repo hops-ops/helm-certmanager --reload +cargo run -- config install --repo hops-ops/helm-certmanager --reload # 8) Apply a pinned remote package version directly (no clone/build) -cargo run -- local config --repo hops-ops/helm-certmanager --version v0.1.0 +cargo run -- config install --repo hops-ops/helm-certmanager --version v0.1.0 # 9) Remove a configuration and prune orphaned package dependencies -cargo run -- local unconfig --repo hops-ops/helm-certmanager +cargo run -- config uninstall --repo hops-ops/helm-certmanager # 10) Generate apis/*/configuration.yaml from upbound.yaml for validation -cargo run -- config generate --path /path/to/project +cargo run -- validate generate-configuration --path /path/to/project + +# 11) Observe an existing XR into a manifest +cargo run -- xr observe --kind AutoEKSCluster --name pat-local --namespace default --aws-region us-east-2 + +# 12) Render adoption patches for managed resources under an existing XR +cargo run -- xr adopt --kind AutoEKSCluster --name pat-local --namespace default + +# 13) Convert an observed/adopted XR into a managed manifest +cargo run -- xr manage --kind AutoEKSCluster --name pat-local --namespace default + +# 14) Render patches that remove Delete from management policies +cargo run -- xr orphan --kind AutoEKSCluster --name pat-local --namespace default ``` ## Commands @@ -132,31 +154,32 @@ cargo run -- config generate --path /path/to/project - Runs `colima delete --force`. - `local uninstall` - Prompts for confirmation, then runs `brew uninstall colima`. -- `local config [--path ] [--reload]` +- `config install [--path ] [--reload]` + - Targets the currently connected Kubernetes cluster - Runs `up project build` in `PATH` (defaults to current directory) - Loads generated `.uppkg` artifacts from `/_output` - - Pushes package images to local registry (`localhost:30500`) + - Pushes package images to the registry exposed at `localhost:30500` - Applies Crossplane `Configuration` resources pointing at `registry.crossplane-system.svc.cluster.local:5000/...` -- `local config --repo [--reload]` +- `config install --repo [--reload]` - Uses local repo cache at `~/.hops/local/repo-cache//` - Clones on first use, then fetches/pulls on subsequent runs - Runs the same build/load/push/apply flow as `--path` - `--reload` - - Forces source-based config (`--path` or `--repo` without `--version`) to delete existing `ConfigurationRevision` resources and matching `Function`/`FunctionRevision` package resources from the same sources, then re-apply the `Configuration` + - Forces source-based config install (`--path` or `--repo` without `--version`) to delete existing `ConfigurationRevision` resources and matching `Function`/`FunctionRevision` package resources from the same sources, then re-apply the `Configuration` - Useful when re-running a config and you want Crossplane to re-create the current revision from source -- `local config --repo --version ` +- `config install --repo --version ` - Skips clone/build and applies `Configuration` with package `ghcr.io//:` - Uses configuration name `-` (for example `hops-ops-helm-certmanager`) - Does not support `--reload` -- `local unconfig --name ` +- `config uninstall --name ` - Deletes the target `Configuration` - Waits for package lock reconciliation - Prunes orphaned `Configuration`/`Function`/`Provider` packages and revisions no longer present in lock - Prunes orphaned `ImageConfig` rewrites for removed render functions -- `local unconfig --repo ` +- `config uninstall --repo ` - Targets configuration name `-` - If cached repo exists at `~/.hops/local/repo-cache//`, derives source hints from it for additional package pruning -- `local unconfig --path ` +- `config uninstall --path ` - Derives target configuration names from `/_output/*.uppkg` image tags - Also derives package sources from those artifacts and prunes matching package resources (including Functions) if they remain - `local aws [--profile ]` @@ -177,10 +200,53 @@ cargo run -- config generate --path /path/to/project - Applies a Secret (`github-creds`) and GitHub `ProviderConfig` (`default`) in namespace `default` - `--refresh` updates only the Secret credentials and skips Provider/ProviderConfig apply - Supports overrides via `--namespace`, `--secret-name`, `--provider-config-name`, `--provider-name`, and `--provider-package` -- `config generate [--path ] [--api-path ]` +- `validate generate-configuration [--path ] [--api-path ]` - Reads `/upbound.yaml` and writes `/configuration.yaml` - Auto-detects `--api-path` via `apis/*/definition.yaml` when omitted - Ensures `apis/**/configuration.yaml` is present in `/.gitignore` (unless `--no-gitignore-update`) +- `xr observe --kind --name --namespace --aws-region ` + - Generates an observe-only XR manifest for an existing resource + - Loads the live XR from the cluster when present + - Enriches the manifest with live AWS discovery for supported XR kinds such as `AutoEKSCluster` and `Network` + - Supports `--output` and `--apply` +- `xr adopt --kind --name --namespace ` + - Lists managed resources that belong to the XR and renders metadata patches needed for adoption + - For `AutoEKSCluster`, uses the composite-specific label `hops.ops.com.ai/autoekscluster=` + - Only emits patches for resources whose external name is missing or blank and can be resolved for that kind + - Supports `--apply`, `--output`, and `--recursive` +- `xr manage --kind --name --namespace ` + - Generates the final managed XR manifest from an observed or adopted XR already in the cluster + - Supports `--output` and `--apply` +- `xr orphan --kind --name --namespace ` + - Renders managed-resource patches that remove `Delete` from management policies + - Supports `--apply` and `--output` + +## XR workflow + +Typical reclaim flow: + +```bash +# 1) Observe the existing resource into an XR manifest +hops xr observe --kind AutoEKSCluster --name pat-local --namespace default --aws-region us-east-2 --output observed.yaml + +# 2) Apply the observe XR if desired +kubectl apply -f observed.yaml + +# 3) Render and apply adoption patches for the next set of managed resources +hops xr adopt --kind AutoEKSCluster --name pat-local --namespace default --apply + +# 4) Repeat adopt until no more patches are needed, or use --recursive +hops xr adopt --kind AutoEKSCluster --name pat-local --namespace default --recursive --apply + +# 5) Convert the XR into a managed manifest +hops xr manage --kind AutoEKSCluster --name pat-local --namespace default --output managed.yaml +``` + +Notes: + +- `xr adopt` only patches resources it can identify for the selected XR kind. +- A blank `crossplane.io/external-name` is treated as missing. +- `AutoEKSCluster` adoption currently resolves identities for supported managed kinds such as IAM attachments and KMS keys. ## Logging diff --git a/src/commands/local/config.rs b/src/commands/config/install.rs similarity index 99% rename from src/commands/local/config.rs rename to src/commands/config/install.rs index fa24ed9..01a1927 100644 --- a/src/commands/local/config.rs +++ b/src/commands/config/install.rs @@ -1,4 +1,4 @@ -use super::{ +use crate::commands::local::{ kubectl_apply_stdin, repo_cache_path, run_cmd, run_cmd_output, sync_registry_hosts_entry, }; use clap::Args; diff --git a/src/commands/config/mod.rs b/src/commands/config/mod.rs index 9eecdf2..47b6801 100644 --- a/src/commands/config/mod.rs +++ b/src/commands/config/mod.rs @@ -1,4 +1,5 @@ -mod generate_configuration; +mod install; +mod uninstall; use clap::{Args, Subcommand}; use std::error::Error; @@ -11,12 +12,15 @@ pub struct ConfigArgs { #[derive(Subcommand, Debug)] pub enum ConfigCommands { - /// Generate api metadata configuration.yaml from upbound.yaml - Generate(generate_configuration::GenerateArgs), + /// Build and load a Crossplane configuration into the local cluster + Install(install::ConfigArgs), + /// Remove a Crossplane configuration and prune orphaned package dependencies + Uninstall(uninstall::UnconfigArgs), } pub fn run(args: &ConfigArgs) -> Result<(), Box> { match &args.command { - ConfigCommands::Generate(generate_args) => generate_configuration::run(generate_args), + ConfigCommands::Install(install_args) => install::run(install_args), + ConfigCommands::Uninstall(uninstall_args) => uninstall::run(uninstall_args), } } diff --git a/src/commands/local/unconfig.rs b/src/commands/config/uninstall.rs similarity index 99% rename from src/commands/local/unconfig.rs rename to src/commands/config/uninstall.rs index f8826c0..07fbd55 100644 --- a/src/commands/local/unconfig.rs +++ b/src/commands/config/uninstall.rs @@ -1,4 +1,4 @@ -use super::{repo_cache_path, run_cmd, run_cmd_output}; +use crate::commands::local::{repo_cache_path, run_cmd, run_cmd_output}; use clap::Args; use serde::Deserialize; use std::collections::{HashMap, HashSet}; diff --git a/src/commands/local/mod.rs b/src/commands/local/mod.rs index d6c14eb..3344e86 100644 --- a/src/commands/local/mod.rs +++ b/src/commands/local/mod.rs @@ -1,12 +1,10 @@ mod aws; -mod config; mod destroy; mod github; mod install; mod reset; mod start; mod stop; -mod unconfig; mod uninstall; use clap::{Args, Subcommand}; @@ -42,10 +40,6 @@ pub enum LocalCommands { Destroy, /// Uninstall Colima Uninstall, - /// Build and load a Crossplane configuration into the local cluster - Config(config::ConfigArgs), - /// Remove a Crossplane configuration and prune orphaned package dependencies - Unconfig(unconfig::UnconfigArgs), } pub fn run(args: &LocalArgs) -> Result<(), Box> { @@ -58,8 +52,6 @@ pub fn run(args: &LocalArgs) -> Result<(), Box> { LocalCommands::Stop => stop::run(), LocalCommands::Destroy => destroy::run(), LocalCommands::Uninstall => uninstall::run(), - LocalCommands::Config(config_args) => config::run(config_args), - LocalCommands::Unconfig(unconfig_args) => unconfig::run(unconfig_args), } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 09c5801..302e41b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod config; pub mod local; +pub mod validate; pub mod xr; diff --git a/src/commands/config/generate_configuration.rs b/src/commands/validate/generate_configuration.rs similarity index 100% rename from src/commands/config/generate_configuration.rs rename to src/commands/validate/generate_configuration.rs diff --git a/src/commands/validate/mod.rs b/src/commands/validate/mod.rs new file mode 100644 index 0000000..0291402 --- /dev/null +++ b/src/commands/validate/mod.rs @@ -0,0 +1,24 @@ +mod generate_configuration; + +use clap::{Args, Subcommand}; +use std::error::Error; + +#[derive(Args, Debug)] +pub struct ValidateArgs { + #[command(subcommand)] + pub command: ValidateCommands, +} + +#[derive(Subcommand, Debug)] +pub enum ValidateCommands { + /// Generate api metadata configuration.yaml from upbound.yaml + GenerateConfiguration(generate_configuration::GenerateArgs), +} + +pub fn run(args: &ValidateArgs) -> Result<(), Box> { + match &args.command { + ValidateCommands::GenerateConfiguration(generate_args) => { + generate_configuration::run(generate_args) + } + } +} diff --git a/src/main.rs b/src/main.rs index 86b2a6e..43b68e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,10 @@ struct Args { enum Commands { /// Manage the local development environment Local(commands::local::LocalArgs), - /// Manage Crossplane configuration project helpers + /// Manage Crossplane configuration packages in the connected cluster Config(commands::config::ConfigArgs), + /// Manage validation helpers for Crossplane projects + Validate(commands::validate::ValidateArgs), /// Manage live XR observe/manage/adopt workflows Xr(commands::xr::XrArgs), } @@ -34,6 +36,9 @@ fn main() -> Result<(), Box> { Some(Commands::Config(config_args)) => { commands::config::run(config_args)?; } + Some(Commands::Validate(validate_args)) => { + commands::validate::run(validate_args)?; + } Some(Commands::Xr(xr_args)) => { commands::xr::run(xr_args)?; }