mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 02:27:01 +09:00
[Backport release-1.7] fix(marketplace): do not install a connected repository's components on connect (#5117)
# Description Backport of #5071 to `release-1.7`.
This commit is contained in:
8 files changed
+124
-176
No files matched your search
@@ -529,12 +529,13 @@ func installPackage(ctx context.Context, k8sClient client.Client, packageSourceN
|
||||
return fmt.Errorf("failed to create Package %s: %w", pkgName, err)
|
||||
}
|
||||
if !created {
|
||||
// The materializer auto-created the registration Package between the
|
||||
// List above and this Create, so this run did not take ownership of it
|
||||
// (its markers are not shed). Re-run so the reopen path pins it -- for a
|
||||
// non-default variant to apply the choice, and for the default variant so
|
||||
// a later privileged flip does not de-register this deliberate install.
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Package %s was just auto-registered; re-run 'cozypkg add %s' to install variant %q as your own\n", pkgName, pkgName, variant)
|
||||
// A Package of this name appeared between the List above and this Create
|
||||
// (a concurrent add, or a tap-managed registration left by an older
|
||||
// Cozystack), so this run did not take ownership of it (its markers are
|
||||
// not shed). Re-run so the reopen path pins it -- for a non-default
|
||||
// variant to apply the choice, and for the default variant so a later
|
||||
// privileged flip does not de-register this deliberate install.
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Package %s already exists; re-run 'cozypkg add %s' to install variant %q as your own\n", pkgName, pkgName, variant)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -157,12 +157,13 @@ cozypkg validate .
|
||||
cozypkg push oci://<registry>/%s:<tag>
|
||||
`+"```"+`
|
||||
|
||||
## Connect (one command, on a Cozystack cluster)
|
||||
## Connect and install (on a Cozystack cluster)
|
||||
|
||||
`+"```bash"+`
|
||||
cozypkg tap oci://<registry>/%s:<tag>
|
||||
cozypkg tap oci://<registry>/%s:<tag> # connect the repository; installs nothing
|
||||
cozypkg add %s # install the app
|
||||
`+"```"+`
|
||||
`, psName, app, app, app, app, app),
|
||||
`, psName, app, app, app, app, app, psName),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+33
-31
@@ -183,9 +183,9 @@ func rewritePackageSourceForTap(ps *cozyv1alpha1.PackageSource, sourceName, sour
|
||||
|
||||
// clearMaterializedRevision removes the operator's materialized-revision
|
||||
// annotation from the tap's OCIRepository, so the next reconcile does not
|
||||
// short-circuit on the unchanged artifact revision and re-materializes (which
|
||||
// re-creates a registration removed out-of-band). A no-op when the source or the
|
||||
// annotation is absent.
|
||||
// short-circuit on the unchanged artifact revision and re-materializes its
|
||||
// PackageSources (recovering one removed out-of-band). A no-op when the source or
|
||||
// the annotation is absent.
|
||||
func clearMaterializedRevision(ctx context.Context, k8sClient client.Client, srcName string) error {
|
||||
reg := &sourcev1.OCIRepository{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: srcName, Namespace: cozySystemNamespace}, reg); err != nil {
|
||||
@@ -228,12 +228,11 @@ var tapCmd = &cobra.Command{
|
||||
creates a Flux OCIRepository pointing at the artifact and the PackageSource(s)
|
||||
the artifact carries, under the names the repository declares. A name that
|
||||
clashes with a core component (or another tap) is rejected, so an external
|
||||
package can never silently shadow an official one. The repository's applications
|
||||
register in the catalog automatically, unless the repository declares no default
|
||||
variant (then nothing is registered). A privileged install component of a
|
||||
registered app is not installed until the operator confirms it with 'cozypkg add
|
||||
--allow-privileged'. Tapping is idempotent. Creating cluster-scoped resources
|
||||
requires cluster-admin.
|
||||
package can never silently shadow an official one. Tapping installs nothing: it
|
||||
makes the repository's apps available as PackageSources. Install an app with
|
||||
'cozypkg add <name>'; a privileged install component is not installed until you
|
||||
confirm it with 'cozypkg add --allow-privileged'. Tapping is idempotent. Creating
|
||||
cluster-scoped resources requires cluster-admin.
|
||||
|
||||
With --secret the OCIRepository is given a pull-credential secretRef (the
|
||||
admin pre-creates the Secret in cozy-system), so a private repository taps in
|
||||
@@ -243,8 +242,8 @@ Trust: tap validates the artifact's structure but does NOT verify its cosign
|
||||
signature. Signature verification is the community index's job at publication
|
||||
time ('cozypkg validate --require-signature' in the index CI gate, pinned to
|
||||
the entry's recorded identity), and can additionally be enforced at pull time
|
||||
via Flux OCIRepository verification. Tapping a third-party repository runs its
|
||||
charts in your management cluster: tap only sources you trust.`,
|
||||
via Flux OCIRepository verification. Installing an app from a tapped repository
|
||||
runs its charts in your management cluster: tap only sources you trust.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
@@ -348,13 +347,10 @@ charts in your management cluster: tap only sources you trust.`,
|
||||
// Force a fresh materialization on this tap: clear the materialized-revision
|
||||
// annotation so the operator does not short-circuit on the unchanged
|
||||
// artifact revision. This makes a re-tap the supported recovery for a
|
||||
// registration Package (or PackageSource) removed out-of-band since the last
|
||||
// materialization -- e.g. garbage-collected when its PackageSource was
|
||||
// deleted. Note this re-registers ANY currently-absent app the artifact
|
||||
// declares, so a re-tap also undoes a `cozypkg del` of a tap-registered app;
|
||||
// the two are indistinguishable (both leave the registration absent), and
|
||||
// there is no per-app opt-out. A first tap has no such annotation, so this is
|
||||
// a no-op then.
|
||||
// PackageSource removed out-of-band since the last materialization. Tapping
|
||||
// no longer installs anything, so a re-tap does NOT re-install an app removed
|
||||
// with `cozypkg del`; re-install it with `cozypkg add`. A first tap has no
|
||||
// such annotation, so this is a no-op then.
|
||||
if err := clearMaterializedRevision(ctx, k8sClient, srcName); err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "warning: could not reset materialization state on OCIRepository/%s: %v\n", srcName, err)
|
||||
}
|
||||
@@ -363,7 +359,11 @@ charts in your management cluster: tap only sources you trust.`,
|
||||
for _, n := range names {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), " PackageSource/%s\n", n)
|
||||
}
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "The repository's applications register in the dashboard catalog automatically. A privileged install component is not installed until you confirm it with 'cozypkg add --allow-privileged'. Check the source's events if an app does not appear.\n")
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Tapping installs nothing. Install an app with:\n")
|
||||
for _, n := range names {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), " cozypkg add %s\n", n)
|
||||
}
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "A privileged install component is not installed until you confirm it with 'cozypkg add --allow-privileged'.\n")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -371,11 +371,13 @@ charts in your management cluster: tap only sources you trust.`,
|
||||
var untapCmd = &cobra.Command{
|
||||
Use: "untap <packagesource-name>",
|
||||
Short: "Remove a tapped repository",
|
||||
Long: `Untap removes a tapped PackageSource, its Flux source, and the
|
||||
tap-managed registration Package that de-registers the repository's apps from
|
||||
the catalog. Running application instances are left in place but become
|
||||
unmanaged until the repository is tapped again. Only tapped sources (marked
|
||||
with the marketplace-tap label) can be untapped; official sources are refused.`,
|
||||
Long: `Untap removes a tapped PackageSource and its Flux source. A tap-managed
|
||||
registration Package left by an older Cozystack (one that registered apps on
|
||||
connect) is removed too, de-registering those apps. An app you installed yourself
|
||||
with 'cozypkg add' is left installed: untap refuses without --yes while such a
|
||||
Package is present. Running application instances are left in place but become
|
||||
unmanaged until the repository is tapped again. Only tapped sources (marked with
|
||||
the marketplace-tap label) can be untapped; official sources are refused.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
k8sClient, err := newClusterClient(tapCmdFlags.kubeconfig)
|
||||
@@ -404,13 +406,13 @@ func runUntap(ctx context.Context, k8sClient client.Client, name string, allowYe
|
||||
srcName = ps.Spec.SourceRef.Name
|
||||
}
|
||||
|
||||
// The registration Package (auto-created on connect, tap-managed)
|
||||
// de-registers the repository's apps and is removed with the untap. Deleting
|
||||
// it garbage-collects its registration HelmReleases and the
|
||||
// ApplicationDefinitions they render. It is removed only when it belongs to
|
||||
// THIS source (label AND source annotation); a Package a later tap created
|
||||
// under a reused name, or a foreign Package, is left in place and blocks the
|
||||
// untap without --yes.
|
||||
// A tap-managed registration Package (left by an older Cozystack that
|
||||
// registered apps on connect) de-registers the repository's apps and is
|
||||
// removed with the untap. Deleting it garbage-collects its registration
|
||||
// HelmReleases and the ApplicationDefinitions they render. It is removed only
|
||||
// when it belongs to THIS source (label AND source annotation); a Package a
|
||||
// later tap created under a reused name, or a foreign Package, is left in place
|
||||
// and blocks the untap without --yes.
|
||||
//
|
||||
// The Package is handled BEFORE the PackageSource: a transient error here must
|
||||
// abort the untap, not fall through to delete the PackageSource and OCIRepository
|
||||
|
||||
@@ -42,4 +42,4 @@ Cosign verification is **mandatory** on the gate for both lanes: the trust ancho
|
||||
|
||||
## Trust model
|
||||
|
||||
Signature verification happens here, at publication time, not at tap time. `cozypkg tap` and the dashboard connect flow validate an artifact's structure but do not verify its cosign signature: a cluster that taps a listed repository trusts that this index already verified it. The verification points are this CI gate (which pins each release to the entry's recorded identity) and, optionally, Flux `OCIRepository` verification at pull time. Tapping a third-party repository runs its charts in the management cluster, so operators should tap only sources they trust or that appear in a curated index like this one.
|
||||
Signature verification happens here, at publication time, not at tap time. `cozypkg tap` and the dashboard connect flow validate an artifact's structure but do not verify its cosign signature: a cluster that taps a listed repository trusts that this index already verified it. The verification points are this CI gate (which pins each release to the entry's recorded identity) and, optionally, Flux `OCIRepository` verification at pull time. Installing an app from a tapped repository runs its charts in the management cluster, so operators should tap only sources they trust or that appear in a curated index like this one.
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
@@ -165,22 +164,14 @@ func (r *TapMaterializerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
if err := r.Patch(ctx, ps, client.Apply, client.FieldOwner(tapFieldOwner), client.ForceOwnership); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("materialize PackageSource %s: %w", ps.GetName(), err)
|
||||
}
|
||||
// Register the repository's apps on connect (not on a later `cozypkg
|
||||
// add`) via a tap-managed Package whose install-marked components (the
|
||||
// ApplicationDefinition registrations) deploy so the apps appear in the
|
||||
// catalog, mirroring how the platform ships a Package per built-in app.
|
||||
// The app components carry no install block and are instantiated per user
|
||||
// resource. Privileged install components are refused for a tap-labelled
|
||||
// (unconfirmed) Package on several best-effort layers: the Package
|
||||
// reconciler skips their HelmRelease and prunes a lingering one at the
|
||||
// install site, and the PackageSource reconciler withholds their
|
||||
// ExternalArtifact (collision.PrivilegedConfirmed). A benign component
|
||||
// flipped to privileged in a new revision can still be materialised by
|
||||
// source-watcher before these observe the flip; fully closing that race
|
||||
// needs the catalog registration decoupled from install (issue #4359).
|
||||
if err := r.ensureRegistration(ctx, ps, &repo); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("register apps for %s: %w", ps.GetName(), err)
|
||||
}
|
||||
// Connecting a repository materializes its PackageSources and nothing
|
||||
// else: no Package, HelmRelease or ApplicationDefinition is created here.
|
||||
// Auto-registering on connect (removed) created a tap-managed Package whose
|
||||
// install-marked components Flux deployed under its cluster-admin service
|
||||
// account, so a publisher's install chart ran on connect with no operator
|
||||
// step (issue #4978). Installing is now explicit: `cozypkg add` creates the
|
||||
// Package. Populating the catalog from PackageSource metadata without an
|
||||
// install is the long-term shape in issue #4359.
|
||||
applied[ps.GetName()] = true
|
||||
logger.Info("materialized PackageSource from tap", "name", ps.GetName(), "tap", repo.Name)
|
||||
}
|
||||
@@ -230,52 +221,6 @@ func (r *TapMaterializerReconciler) failMaterialize(ctx context.Context, repo *s
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// registrationOwnerRef builds the ownerReference from a registration Package to
|
||||
// its PackageSource, so the Package is garbage-collected if the PackageSource is
|
||||
// deleted directly.
|
||||
func registrationOwnerRef(ps *cozyv1alpha1.PackageSource) metav1.OwnerReference {
|
||||
return metav1.OwnerReference{
|
||||
APIVersion: cozyv1alpha1.GroupVersion.String(),
|
||||
Kind: "PackageSource",
|
||||
Name: ps.GetName(),
|
||||
UID: ps.GetUID(),
|
||||
}
|
||||
}
|
||||
|
||||
// autoRegisterSkipReason returns why a PackageSource's apps cannot be
|
||||
// auto-registered on connect, or "" when they can. Only a source with no
|
||||
// "default" variant is skipped (nothing to register by default). Privileged
|
||||
// components are NOT a reason to skip: the source is registered and its
|
||||
// privileged components are refused at install time (collision.PrivilegedConfirmed)
|
||||
// until the operator confirms with 'cozypkg add --allow-privileged'.
|
||||
func autoRegisterSkipReason(ps *cozyv1alpha1.PackageSource) string {
|
||||
if !hasDefaultVariant(ps) {
|
||||
return `it declares no "default" variant; register it manually with 'cozypkg add'`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// warnNotAutoRegistered records on the source, as a Warning Event, that a
|
||||
// materialized PackageSource's apps were not auto-registered, with the reason.
|
||||
// This Event is the immediate, human-facing signal.
|
||||
func (r *TapMaterializerReconciler) warnNotAutoRegistered(repo *sourcev1.OCIRepository, ps *cozyv1alpha1.PackageSource, reason string) {
|
||||
if r.Recorder != nil {
|
||||
r.Recorder.Event(repo, corev1.EventTypeWarning, "AppsNotAutoRegistered",
|
||||
fmt.Sprintf("PackageSource %s: %s", ps.GetName(), reason))
|
||||
}
|
||||
}
|
||||
|
||||
// hasDefaultVariant reports whether the PackageSource declares a variant named
|
||||
// "default".
|
||||
func hasDefaultVariant(ps *cozyv1alpha1.PackageSource) bool {
|
||||
for i := range ps.Spec.Variants {
|
||||
if ps.Spec.Variants[i].Name == "default" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// deleteMaterialized removes every PackageSource materialized from the given tap
|
||||
// source, and each one's tap-managed registration Package. A user's own Package
|
||||
// (no tap label) is left in place.
|
||||
@@ -445,29 +390,3 @@ func (r *TapMaterializerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
For(&sourcev1.OCIRepository{}, builder.WithPredicates(isTap)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// ensureRegistration creates the tap-managed registration Package for a
|
||||
// materialized PackageSource so its apps appear in the catalog, unless the source
|
||||
// has no "default" variant (nothing to register), in which case a Warning Event
|
||||
// says why. Privileged components are not gated here: the Package reconciler
|
||||
// refuses them for an unconfirmed tap Package at the install site. The Package is
|
||||
// created only when absent; an existing registration, or a user's own or pinned
|
||||
// Package, is left as is.
|
||||
func (r *TapMaterializerReconciler) ensureRegistration(ctx context.Context, ps *cozyv1alpha1.PackageSource, repo *sourcev1.OCIRepository) error {
|
||||
if skip := autoRegisterSkipReason(ps); skip != "" {
|
||||
r.warnNotAutoRegistered(repo, ps, "not auto-registered because "+skip)
|
||||
return nil
|
||||
}
|
||||
pkg := &cozyv1alpha1.Package{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ps.GetName(),
|
||||
Labels: map[string]string{tapconst.Label: "true"},
|
||||
Annotations: map[string]string{tapconst.SourceAnnotation: repo.Name},
|
||||
OwnerReferences: []metav1.OwnerReference{registrationOwnerRef(ps)},
|
||||
},
|
||||
}
|
||||
if err := r.Create(ctx, pkg); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -299,24 +299,6 @@ func TestDeleteRegistrationPackageLosesRaceToHandover(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDefaultVariant(t *testing.T) {
|
||||
with := &cozyv1alpha1.PackageSource{Spec: cozyv1alpha1.PackageSourceSpec{
|
||||
Variants: []cozyv1alpha1.Variant{{Name: "big"}, {Name: "default"}},
|
||||
}}
|
||||
if !hasDefaultVariant(with) {
|
||||
t.Error("expected a default variant to be detected")
|
||||
}
|
||||
without := &cozyv1alpha1.PackageSource{Spec: cozyv1alpha1.PackageSourceSpec{
|
||||
Variants: []cozyv1alpha1.Variant{{Name: "only"}, {Name: "second"}},
|
||||
}}
|
||||
if hasDefaultVariant(without) {
|
||||
t.Error("expected no default variant")
|
||||
}
|
||||
if hasDefaultVariant(&cozyv1alpha1.PackageSource{}) {
|
||||
t.Error("expected no default variant for an empty source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegedInstallComponents(t *testing.T) {
|
||||
ps := &cozyv1alpha1.PackageSource{Spec: cozyv1alpha1.PackageSourceSpec{
|
||||
Variants: []cozyv1alpha1.Variant{{
|
||||
@@ -337,18 +319,20 @@ func TestPrivilegedInstallComponents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReconcileRegistersApps asserts the happy path: a materialized tap creates
|
||||
// a tap-managed registration Package so the repository's apps register in the
|
||||
// catalog on connect, without a manual `cozypkg add`.
|
||||
func TestReconcileRegistersApps(t *testing.T) {
|
||||
// TestReconcileCreatesNoPackageOnConnect asserts that connecting a repository
|
||||
// materializes its PackageSource and nothing else: no Package (and so no
|
||||
// HelmRelease or ApplicationDefinition) is created on connect. Auto-registering
|
||||
// on connect ran a publisher's install chart under Flux's cluster-admin account
|
||||
// with no operator step (issue #4978); installing is now explicit via
|
||||
// `cozypkg add`.
|
||||
func TestReconcileCreatesNoPackageOnConnect(t *testing.T) {
|
||||
scheme := tapScheme(t)
|
||||
data, digest := tarGz(t, map[string]string{
|
||||
// samplePS declares PackageSource "example.hello" with a default variant.
|
||||
"packages/core/platform/sources/hello.yaml": samplePS,
|
||||
})
|
||||
repo := tapRepo(true)
|
||||
cl := fake.NewClientBuilder().WithScheme(scheme).
|
||||
WithObjects(repo).
|
||||
WithObjects(tapRepo(true)).
|
||||
WithStatusSubresource(&sourcev1.OCIRepository{}).
|
||||
Build()
|
||||
|
||||
@@ -374,25 +358,57 @@ func TestReconcileRegistersApps(t *testing.T) {
|
||||
if err := cl.Get(context.Background(), client.ObjectKey{Name: "example.hello"}, &cozyv1alpha1.PackageSource{}); err != nil {
|
||||
t.Fatalf("PackageSource not materialized: %v", err)
|
||||
}
|
||||
// A tap-managed registration Package was created for it.
|
||||
// No Package is created: connect installs nothing.
|
||||
var pkg cozyv1alpha1.Package
|
||||
if err := cl.Get(context.Background(), client.ObjectKey{Name: "example.hello"}, &pkg); err != nil {
|
||||
t.Fatalf("registration Package not created: %v", err)
|
||||
if err := cl.Get(context.Background(), client.ObjectKey{Name: "example.hello"}, &pkg); !apierrors.IsNotFound(err) {
|
||||
t.Fatalf("connect must create no Package, got err=%v pkg=%+v", err, pkg)
|
||||
}
|
||||
if pkg.GetLabels()[tapconst.Label] != "true" {
|
||||
t.Errorf("registration Package must be tap-managed (labelled), got %v", pkg.GetLabels())
|
||||
}
|
||||
|
||||
// TestReconcileDoesNotRecreateDeletedPackage asserts that a Package the admin
|
||||
// removed (e.g. with `cozypkg del`) stays removed across a new source revision.
|
||||
// Auto-registration used to re-create it on every revision (the second effect in
|
||||
// issue #4978); with it gone the materializer never creates a Package at all.
|
||||
func TestReconcileDoesNotRecreateDeletedPackage(t *testing.T) {
|
||||
scheme := tapScheme(t)
|
||||
data, digest := tarGz(t, map[string]string{
|
||||
"packages/core/platform/sources/hello.yaml": samplePS,
|
||||
})
|
||||
cur := data
|
||||
cl := fake.NewClientBuilder().WithScheme(scheme).
|
||||
WithObjects(tapRepo(true)).
|
||||
WithStatusSubresource(&sourcev1.OCIRepository{}).
|
||||
Build()
|
||||
r := materializerFor(scheme, cl, record.NewFakeRecorder(10), &cur)
|
||||
|
||||
// Connect (rev1): materializes the PackageSource, creates no Package.
|
||||
setArtifact(t, cl, digest, "rev1")
|
||||
if _, err := r.Reconcile(context.Background(), req()); err != nil {
|
||||
t.Fatalf("reconcile rev1: %v", err)
|
||||
}
|
||||
if pkg.GetAnnotations()[tapconst.SourceAnnotation] != repo.Name {
|
||||
t.Errorf("registration Package must record its source, got %v", pkg.GetAnnotations())
|
||||
|
||||
// Admin installs then removes the app: a plain user Package appears and is
|
||||
// then deleted with `cozypkg del`.
|
||||
userPkg := &cozyv1alpha1.Package{ObjectMeta: metav1.ObjectMeta{Name: "example.hello"}}
|
||||
if err := cl.Create(context.Background(), userPkg); err != nil {
|
||||
t.Fatalf("create user Package: %v", err)
|
||||
}
|
||||
// Variant is left empty so the Package reconciler resolves it to "default"
|
||||
// deterministically, rather than pinning whichever variant was listed first.
|
||||
if pkg.Spec.Variant != "" {
|
||||
t.Errorf("registration Package must leave Variant empty, got %q", pkg.Spec.Variant)
|
||||
if err := cl.Delete(context.Background(), userPkg); err != nil {
|
||||
t.Fatalf("delete user Package: %v", err)
|
||||
}
|
||||
// Owned by its PackageSource so a direct delete of the PackageSource GCs it.
|
||||
if len(pkg.OwnerReferences) != 1 || pkg.OwnerReferences[0].Kind != "PackageSource" || pkg.OwnerReferences[0].Name != "example.hello" {
|
||||
t.Errorf("registration Package must own-ref its PackageSource, got %+v", pkg.OwnerReferences)
|
||||
|
||||
// A new revision must not resurrect the deleted Package.
|
||||
data2, digest2 := tarGz(t, map[string]string{
|
||||
"packages/core/platform/sources/hello.yaml": samplePS,
|
||||
})
|
||||
cur = data2
|
||||
setArtifact(t, cl, digest2, "rev2")
|
||||
if _, err := r.Reconcile(context.Background(), req()); err != nil {
|
||||
t.Fatalf("reconcile rev2: %v", err)
|
||||
}
|
||||
var pkg cozyv1alpha1.Package
|
||||
if err := cl.Get(context.Background(), client.ObjectKey{Name: "example.hello"}, &pkg); !apierrors.IsNotFound(err) {
|
||||
t.Fatalf("a removed Package must stay removed across revisions, got err=%v pkg=%+v", err, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,10 +438,10 @@ func setArtifact(t *testing.T, cl client.Client, digest, rev string) {
|
||||
}
|
||||
|
||||
// TestReconcileLeavesUnmarkedPackage: an unmarked Package of the same name (a
|
||||
// user's `cozypkg add`, or one from before this version) means registration is
|
||||
// already satisfied. The reconcile must NOT error (which would hot-loop the whole
|
||||
// tap and re-pull the artifact every backoff), NOT adopt it, and NOT delete it;
|
||||
// the revision is stamped so the loop does not recur.
|
||||
// user's `cozypkg add`, or one from before this version) must be left untouched
|
||||
// by materialization. The reconcile must NOT error (which would hot-loop the
|
||||
// whole tap and re-pull the artifact every backoff), NOT adopt it, and NOT delete
|
||||
// it; the revision is stamped so the loop does not recur.
|
||||
func TestReconcileLeavesUnmarkedPackage(t *testing.T) {
|
||||
scheme := tapScheme(t)
|
||||
data, digest := tarGz(t, map[string]string{"packages/core/platform/sources/hello.yaml": samplePS})
|
||||
|
||||
@@ -62,8 +62,9 @@ export function TapsPage() {
|
||||
</div>
|
||||
|
||||
<div className="mb-4 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
Connecting a repository runs its charts in the management cluster. Connect only sources you
|
||||
trust. The index verifies signatures at publication time; connecting does not verify them.
|
||||
Connecting a repository makes its apps available but installs nothing. Install an app with{" "}
|
||||
<code>cozypkg add</code>. Connect only sources you trust. The index verifies signatures at
|
||||
publication time; connecting does not verify them.
|
||||
</div>
|
||||
|
||||
<div className="mb-6 flex flex-wrap items-end gap-2 rounded-lg border border-slate-200 bg-white p-4">
|
||||
@@ -206,6 +207,12 @@ function TapCard({ tap, onDisconnect }: { tap: Tap; onDisconnect: (name: string)
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{packages.length === 0 && (
|
||||
<p className="mt-3 text-xs text-slate-500">
|
||||
No apps installed from this repository yet. Install one with <code>cozypkg add</code>.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -258,8 +258,10 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
|
||||
// Update only the fields this API owns (spec, the tap label and name
|
||||
// annotation) on the FETCHED object, so the operator's finalizer survives.
|
||||
// Clear the materialized-revision annotation so a re-connect re-materializes
|
||||
// and recovers a registration Package (or PackageSource) removed out-of-band
|
||||
// since the last materialization, the same recovery `cozypkg tap` performs.
|
||||
// and recovers a PackageSource removed out-of-band since the last
|
||||
// materialization, the same recovery `cozypkg tap` performs. Connecting
|
||||
// installs nothing, so this does not re-install an app removed with
|
||||
// `cozypkg del`.
|
||||
cur.Object["spec"] = repo.Object["spec"]
|
||||
labels := cur.GetLabels()
|
||||
if labels == nil {
|
||||
|
||||
Reference in new issue
Block a user