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
61 changes: 20 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,44 +147,14 @@ How it works:
- Applies a GitHub `ProviderConfig` named `default` unless `--refresh` is used.
- Supports overrides for namespace, Secret name, ProviderConfig name, provider name, and provider package.

## Quick Start

```bash
# Build and load a Crossplane configuration package from an Upbound-format XRD project
hops config install --path /path/to/project

# Build from a GitHub repo containing an Upbound-format XRD project
hops config install --repo hops-ops/helm-certmanager

# Force reload from source (deletes existing ConfigurationRevision(s) first)
hops config install --repo hops-ops/helm-certmanager --reload

# Apply a pinned remote package version directly (no clone/build)
hops config install --repo hops-ops/helm-certmanager --version v0.1.0

# Remove a configuration and prune orphaned package dependencies
hops config uninstall --repo hops-ops/helm-certmanager

# Generate apis/*/configuration.yaml from upbound.yaml for validation
hops validate generate-configuration --path /path/to/project

# Observe an existing XR into a manifest
hops xr observe --kind AutoEKSCluster --name pat-local --namespace default --aws-region us-east-2

# Render adoption patches for managed resources under an existing XR
hops xr adopt --kind AutoEKSCluster --name pat-local --namespace default

# Convert an observed/adopted XR into a managed manifest
hops xr manage --kind AutoEKSCluster --name pat-local --namespace default

# Render patches that remove Delete from management policies
hops xr orphan --kind AutoEKSCluster --name pat-local --namespace default
```

## Config packages

`config install` and `config uninstall` operate on the currently connected Kubernetes cluster.
`config install` expects an Upbound-format XRD project when building from source via `--path` or `--repo`.

There are two different `config install` modes:

- Source-build mode via `--path` or `--repo` builds an Upbound-format XRD project locally, pushes the package through the local registry flow, and is intended for a local control plane started with `hops local start`.
- Remote-package mode via `--repo ... --version ...` skips the build and applies a pinned package reference directly, so it can work against non-local connected clusters too.

Common install flows:

Expand All @@ -196,23 +166,26 @@ hops config install
hops config install --path /path/to/project

# Build from a cached GitHub repo checkout containing an Upbound-format XRD project
hops config install --repo hops-ops/helm-certmanager
hops config install --repo hops-ops/aws-auto-eks-cluster

# Force a source reload before re-applying
hops config install --repo hops-ops/helm-certmanager --reload
hops config install --repo hops-ops/aws-auto-eks-cluster --reload

# Set spec.skipDependencyResolution=true on the generated Configuration
hops config install --path /path/to/project --skip-dependency-resolution

# Apply a pinned remote package directly from ghcr.io
hops config install --repo hops-ops/helm-certmanager --version v0.1.0
hops config install --repo hops-ops/aws-auto-eks-cluster --version v0.11.0
```

Common uninstall flows:

```bash
# Remove by explicit configuration name
hops config uninstall --name hops-ops-helm-certmanager
hops config uninstall --name hops-ops-aws-auto-eks-cluster

# Remove by repo slug
hops config uninstall --repo hops-ops/helm-certmanager
hops config uninstall --repo hops-ops/aws-auto-eks-cluster

# Remove configurations derived from local build artifacts
hops config uninstall --path /path/to/project
Expand All @@ -221,6 +194,7 @@ hops config uninstall --path /path/to/project
Notes:

- `--reload` only applies to source installs: `--path` or `--repo` without `--version`.
- `--skip-dependency-resolution` sets `spec.skipDependencyResolution=true` on the generated `Configuration`.
- `config install --repo ... --version ...` skips clone/build and applies the remote package directly.
- `config uninstall --repo ...` derives the configuration name as `<org>-<repo>`.

Expand All @@ -244,21 +218,26 @@ Notes:
- Prompts for confirmation, then runs `brew uninstall colima`.
- `config install [--path <PATH>] [--reload]`
- Targets the currently connected Kubernetes cluster
- Source-build mode intended for a local control plane because it depends on the local registry flow
- Runs `up project build` in `PATH` (defaults to current directory)
- Loads generated `.uppkg` artifacts from `<PATH>/_output`
- Pushes package images to the registry exposed at `localhost:30500`
- Applies Crossplane `Configuration` resources pointing at `registry.crossplane-system.svc.cluster.local:5000/...`
- Supports `--skip-dependency-resolution`
- `config install --repo <org/repo> [--reload]`
- Source-build mode intended for a local control plane because it depends on the local registry flow
- Uses local repo cache at `~/.hops/local/repo-cache/<org>/<repo>`
- 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 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
- `config install --repo <org/repo> --version <tag>`
- Remote-package mode that can target any connected cluster
- Skips clone/build and applies `Configuration` with package `ghcr.io/<org>/<repo>:<tag>`
- Uses configuration name `<org>-<repo>` (for example `hops-ops-helm-certmanager`)
- Uses configuration name `<org>-<repo>` (for example `hops-ops-aws-auto-eks-cluster`)
- Does not support `--reload`
- Supports `--skip-dependency-resolution`
- `config uninstall --name <configuration-name>`
- Deletes the target `Configuration`
- Waits for package lock reconciliation
Expand Down
51 changes: 42 additions & 9 deletions src/commands/config/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pub struct ConfigArgs {
/// Force reload from source by recreating ConfigurationRevision(s) before apply (path/repo only)
#[arg(long, conflicts_with = "version")]
pub reload: bool,

/// Set spec.skipDependencyResolution=true on the generated Configuration
#[arg(long)]
pub skip_dependency_resolution: bool,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -127,9 +131,15 @@ pub fn run(args: &ConfigArgs) -> Result<(), Box<dyn Error>> {
validate_reload_args(args)?;

match (args.repo.as_deref(), args.version.as_deref()) {
(Some(repo), Some(version)) => apply_repo_version(repo, version),
(Some(repo), None) => run_repo_clone(repo, args.reload),
(None, _) => run_local_path(args.path.as_deref().unwrap_or("."), args.reload),
(Some(repo), Some(version)) => {
apply_repo_version(repo, version, args.skip_dependency_resolution)
}
(Some(repo), None) => run_repo_clone(repo, args.reload, args.skip_dependency_resolution),
(None, _) => run_local_path(
args.path.as_deref().unwrap_or("."),
args.reload,
args.skip_dependency_resolution,
),
}
}

Expand All @@ -140,10 +150,18 @@ fn validate_reload_args(args: &ConfigArgs) -> Result<(), Box<dyn Error>> {
Ok(())
}

fn run_repo_clone(repo: &str, reload: bool) -> Result<(), Box<dyn Error>> {
fn run_repo_clone(
repo: &str,
reload: bool,
skip_dependency_resolution: bool,
) -> Result<(), Box<dyn Error>> {
let spec = parse_repo_spec(repo)?;
let cache_path = ensure_cached_repo_checkout(&spec)?;
run_local_path(&cache_path.to_string_lossy(), reload)
run_local_path(
&cache_path.to_string_lossy(),
reload,
skip_dependency_resolution,
)
}

fn ensure_cached_repo_checkout(spec: &RepoSpec) -> Result<PathBuf, Box<dyn Error>> {
Expand Down Expand Up @@ -202,7 +220,11 @@ fn refresh_cached_repo(cache_path: &Path) -> Result<(), Box<dyn Error>> {
Ok(())
}

fn apply_repo_version(repo: &str, version: &str) -> Result<(), Box<dyn Error>> {
fn apply_repo_version(
repo: &str,
version: &str,
skip_dependency_resolution: bool,
) -> Result<(), Box<dyn Error>> {
let spec = parse_repo_spec(repo)?;
let version = version.trim();
if version.is_empty() {
Expand All @@ -215,7 +237,12 @@ fn apply_repo_version(repo: &str, version: &str) -> Result<(), Box<dyn Error>> {
sanitize_name_component(&spec.org),
sanitize_name_component(&spec.repo)
);
apply_configuration(&config_name, &package_ref, false, false)
apply_configuration(
&config_name,
&package_ref,
skip_dependency_resolution,
false,
)
}

fn parse_repo_spec(repo: &str) -> Result<RepoSpec, Box<dyn Error>> {
Expand Down Expand Up @@ -261,7 +288,11 @@ fn sanitize_name_component(input: &str) -> String {
}
}

fn run_local_path(path: &str, reload: bool) -> Result<(), Box<dyn Error>> {
fn run_local_path(
path: &str,
reload: bool,
skip_dependency_resolution: bool,
) -> Result<(), Box<dyn Error>> {
let dir = Path::new(path);
if !dir.is_dir() {
return Err(format!("{} is not a directory", path).into());
Expand Down Expand Up @@ -452,7 +483,7 @@ spec:
for pull_ref in &config_pull_refs {
let (img_path, _) = split_ref(pull_ref);
let name = img_path.rsplit('/').next().unwrap_or(img_path);
apply_configuration(name, pull_ref, false, reload)?;
apply_configuration(name, pull_ref, skip_dependency_resolution, reload)?;
}

Ok(())
Expand Down Expand Up @@ -1121,6 +1152,7 @@ spec:
repo: Some("hops-ops/helm-certmanager".to_string()),
version: Some("v0.1.0".to_string()),
reload: true,
skip_dependency_resolution: false,
};
assert!(validate_reload_args(&args).is_err());
}
Expand All @@ -1132,6 +1164,7 @@ spec:
repo: None,
version: None,
reload: true,
skip_dependency_resolution: false,
};
assert!(validate_reload_args(&args).is_ok());
}
Expand Down
Loading