Releases: hops-ops/hops-cli
v0.30.0
What's changed in v0.30.0
-
feat(local): label
hops provider installresources so doctor knows they're intentional (#54) (by @patrickleet)hops provider installnow stampsapp.kubernetes.io/managed-by: hops-provider-install
on the Provider, DeploymentRuntimeConfig, and ClusterRoleBinding it manages. A
custom install (e.g. a forked provider built from source) uses the
<provider>-runtimeDRC convention rather than the bootstraplocal-dev-<provider>
DRC, whichhops local doctorpreviously reported as drift.hops local doctornow reads that label: a labeled provider is checked against the
install convention (<provider>-runtimeDRC,<provider>-cluster-adminbinding,
SA<provider>) and reported as a "custom install" with its package, instead of
false drift — while still verifying cluster-admin and still flagging real drift
(runtimeConfigRef=default / missing cluster-admin) in both modes.Co-authored-by: Claude Opus 4.8 (1M context) noreply@anthropic.com
See full diff: v0.29.0...v0.30.0
v0.29.0
What's changed in v0.29.0
-
feat(local): per-provider DRCs,
hops local doctor, and global --context (#53) (by @patrickleet)Split the shared
local-devDeploymentRuntimeConfig into per-provider DRCs
(local-dev-kubernetes, local-dev-helm), each with its own cluster-admin
ServiceAccount + ClusterRoleBinding, and point each provider's runtimeConfigRef
at its own DRC. A shared DRC let one provider's runtime image/SA silently
clobber the other's pod.Add
hops local doctor: verifies whathops local startset up — crossplane,
both providers (installed / healthy / runtimeConfigRef pinned to its own DRC /
DRC present / cluster-admin binding / ProviderConfig) and the registry — and
reports drift with a non-zero exit + remediation. Catches a provider whose
runtimeConfigRef reverted todefault, dropping its cluster-admin SA (which
breaks observing XRs through the in-cluster ProviderConfig).Add a global
--contextflag tohops localso every subcommand can target a
context (e.g.hops local aws --refresh --profile hops --context colima), given
before or after the subcommand. Plumbs through HOPS_KUBE_CONTEXT_ENV like
config/provider install.Co-authored-by: Claude Opus 4.8 (1M context) noreply@anthropic.com
See full diff: v0.28.0...v0.29.0
v0.28.0
What's changed in v0.28.0
-
feat(local): add
hops local listmonk+ make --source-context required (#51) (by @patrickleet)Mirrors
hops local zitadel/hops local github. Bootstraps the
hops-ops/provider-listmonk Crossplane provider + a cluster-scoped
ProviderConfig pointing at a Listmonk instance via Basic-Auth
credentials (JSON Secret).Credential resolution waterfall:
- Explicit --endpoint / --username / --token flags
- LISTMONK_{ENDPOINT,USERNAME,TOKEN} env vars
- Read from the chart-bootstrapped Secret on a source cluster
(with keysusername+token— the shape produced by
listmonk-chart v0.2.0's post-install api-user-bootstrap hook)
Endpoint is derived from the source Secret name when not explicitly
set:<release>-provider-creds→ in-cluster service
http://<release>.<source-namespace>.svc.cluster.local:9000.Default upjet provider package: ghcr.io/hops-ops/provider-listmonk:v0.0.3.
Also drops the
pat-localdefault from--source-contexton BOTH
this command andhops local zitadel— that hardcoded value bakes
the implementer's personal cluster name into a tool meant for
multiple users. Required positional flag now; users explicitly pass
their own source context.Verified end-to-end on pat-local 2026-05-25:
- Provider install + Healthy
- ProviderConfig applied
- UserRole MR reconciled (Crossplane → upjet → TF provider →
Listmonk REST API → users / roles table) - User MR reconciled with cross-resource userRoleIdRef → numeric
userRoleId (typed-reference resolution works end-to-end) - AppSettings MR reconciled (no-op write of current values; round-
trip lossless)
See full diff: v0.27.0...v0.28.0
v0.27.0
What's changed in v0.27.0
-
feat: add local zitadel provider bootstrap (#49) (by @patrickleet)
-
feat: add local zitadel provider bootstrap
-
fix: preserve zitadel domain ports
-
See full diff: v0.26.1...v0.27.0
v0.26.1
What's changed in v0.26.1
-
chore(deps): update rust crate openssl-sys to v0.9.116 (#43) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
-
chore(deps): update rust crate tokio to v1.52.3 (#44) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
-
chore(deps): update unbounded-tech/workflow-vnext-tag action to v1.21.3 (#45) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
-
chore(deps): update unbounded-tech/workflows-rust action to v2.3.0 (#46) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
-
chore(deps): update rust crate tar to v0.4.46 (#47) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
-
fix(deps): update rust crate serde_json to v1.0.150 (#48) (by @renovate[bot])
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
See full diff: v0.26.0...v0.26.1
v0.26.0
What's changed in v0.26.0
-
feat(secrets): three-bucket list with platform-pushed section, symmetric managed-by tagging (by @patrickleet)
hops secrets sync awsnow writeshops.ops.com.ai/managed-by=sync
alongside the existinghops.ops.com.ai/secret=true, mirroring the
managed-by=pushsecrettag that AuthStack-style Crossplane stacks
attach to ESO-pushed AWS SM entries. Bucketing insecrets list
becomes a positive match instead of "secret=true minus everything
else."secrets listgains a "Platform-pushed secrets" section between the
existing "Managed secrets" and "Other AWS secrets" sections. Routing
byhops.ops.com.ai/managed-by:- sync (or absent + secret=true for legacy entries) → Managed
- pushsecret → Platform-pushed
- everything else → Other
Platform-pushed columns: Name | Owner | Cluster | Namespace | KMS Key |
Status.Owneris derived from the explicithops.ops.com.ai/kind+
hops.ops.com.ai/nametags (renders e.g.AuthStack/pat-local); falls
back to scanning for the olderhops.ops.com.ai/<kind.lower>=<name>
label for entries pushed before the explicit tags were added.has_managed_secret_tag()(used bysync --cleanupto decide which
remote secrets to consider for deletion) was tightened to require
eithermanaged-by=syncOR no managed-by tag at all. Previously it
matched anysecret=trueentry, which would have swept up PushSecret-
managed entries the moment we addedsecret=trueto that flow.Existing managed secrets show
remote tags differin the list output
until the nexthops secrets sync awswrites the new managed-by tag.
No data is at risk — the migration is just additive.Pairs with the AuthStack PushSecret commit + the SecretStack
push/*IAM grant.
See full diff: v0.25.1...v0.26.0
v0.25.1
What's changed in v0.25.1
-
fix(provider install): never reuse a Provider's existing DRC ref (by @patrickleet)
apply_provider_resources now always names the DRC -runtime
instead of inheriting the existing Provider's spec.runtimeConfigRef.name.
A previous "avoid orphaning the DRC" heuristic blindly reused whatever
DRC the upstream Provider already referenced, then overwrote that DRC
with the new install's image — silently corrupting any other Provider
that happened to reference the same DRC.Repro (the bug that surfaced this): provider-helm and provider-kubernetes
both referenced DRC "local-dev"; a later 'hops provider install helm'
wrote local-dev with the helm dev image, leaving provider-kubernetes
pinned to that DRC and therefore running the helm binary in a pod
labeled as kubernetes.After the fix:
- Each install creates an owned DRC named <existing_provider>-runtime
- If the existing Provider already pointed at a differently-named DRC,
a log::warn surfaces the migration ("switching to owned DRC ...; the
old DRC is not deleted") so leftovers are discoverable - The orphaned DRC is left in place; it may still be referenced by
another Provider, and deletion is the operator's call
Validated end-to-end on colima: 'hops provider install --repo
jonasz-lasut/provider-helm' migrated provider-helm from local-dev to
crossplane-contrib-provider-helm-runtime, helm pod healthy on
v1.999.2, provider-kubernetes (after clearing its stale local-dev ref)
healthy on upstream provider-kubernetes:v1.2.0.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
See full diff: v0.25.0...v0.25.1
v0.25.0
What's changed in v0.25.0
-
feat(secrets): --secret-path scope on encrypt/decrypt (by @patrickleet)
SOPS encryption is non-deterministic — re-encrypting unchanged
plaintexts produces different ciphertext on every run, polluting the
git diff with files the operator didn't actually touch.hops secrets encryptanddecryptnow accept an optional--secret-paththat
scopes the traversal to a single file or subdirectory inside the
configured source root.encrypt only the AuthStack durable secrets, no spurious diffs
hops secrets encrypt --secret-path secrets/aws/pat-local/zitadel
symmetric — decrypt one subtree for inspection
hops secrets decrypt --secret-path secrets-encrypted/aws/pat-local
Validates the scope resolves inside the source root before running.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
-
refactor(auth): bootstrap writes flat files (one shared AWS SM secret) (by @patrickleet)
Both durable AuthStack values now live as JSON properties under a
single AWS SM secret. Bootstrap writes flat plaintext files instead
of single-file directories sohops secrets sync awsgroups them
into one AWS SM blob:secrets/aws//zitadel/masterkey
secrets/aws//zitadel/admin-password→ AWS SM
<cluster>/zitadel:
{ "masterkey": "...", "admin-password": "..." }Drops the
masterkey/masterkeyandadmin-password/passworddirectory
shapes — the property-name=directory-name redundancy was awkward and
the values are seeded together / rotate together, so grouping them as
one secret is the natural fit.Pairs with the AuthStack composition's collapsed
externalSecrets.secretPath.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
See full diff: v0.24.2...v0.25.0
v0.24.2
What's changed in v0.24.2
-
refactor(auth): bootstrap writes plaintext, not AWS SM (by @patrickleet)
hops auth bootstrap <cluster>now generates the durable AuthStack
secret plaintexts into the repo's secrets/ tree, matching the AWS
secret-path conventions the AuthStack composition's ExternalSecrets
expect:secrets/aws//zitadel/masterkey/masterkey
secrets/aws//zitadel/admin-password/passwordThe platform's existing pipeline takes it from there:
hops secrets encrypt # SOPS-encrypts into secrets-encrypted/
hops secrets sync aws # pushes to AWS Secrets ManagerThis collapses two bootstrap pathways into one, makes the durable
secrets reviewable in git (as SOPS-encrypted), and removes the
direct AWS SDK dependency from this command.Idempotency unchanged: existing plaintexts are left alone unless
--force.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
See full diff: v0.24.1...v0.24.2
v0.24.1
What's changed in v0.24.1
-
fix(auth): SSO-aware AWS creds + Zitadel-compliant password generator
Two follow-ups from end-to-end testing of
hops auth bootstrap:- Switch from a raw rusoto ChainProvider to reusing
secrets::aws_clients
so bootstrap resolves SSO profiles the same wayhops secretsdoes
(AWS_PROFILE → aws configure export-credentials → StaticProvider).
Makesbootstrapwork cleanly underAWS_PROFILE=hopsagainst the
SSO-only hops account. - Add a
generate_complex_passwordthat satisfies Zitadel's default
policy (HasUppercase + HasLowercase + HasNumber + HasSymbol). The
prior generator emitted hex (digits + lowercase only); Zitadel's
Human user creation rejected those. - Pass a UUID client_request_token to both CreateSecret and
PutSecretValue (AWS SM requires it on PutSecretValue too). - Unit tests cover all four character classes across 200 rolls.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Switch from a raw rusoto ChainProvider to reusing
See full diff: v0.24.0...v0.24.1