From 78bb6d5da37066153d20fdff7f762599fc484ac5 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Tue, 12 Nov 2024 18:21:31 +0000 Subject: [PATCH 01/13] Initial cut at porch core refactor --- pkg/cache/cache.go | 20 +- pkg/cache/fake/objectnotifier.go | 3 +- pkg/cache/memory/cache.go | 4 +- pkg/cache/memory/cache_test.go | 3 +- pkg/cache/memory/draft.go | 2 - pkg/cache/memory/package.go | 2 - pkg/cache/memory/packagerevision.go | 53 +- pkg/cache/memory/repository.go | 38 +- pkg/cache/memory/util.go | 1 + pkg/engine/doc.go | 17 - pkg/engine/engine.go | 961 ++---------------- pkg/engine/options.go | 29 +- pkg/engine/package.go | 76 -- pkg/engine/testdata/clone/bucket/Kptfile | 20 - pkg/engine/testdata/clone/bucket/bucket.yaml | 26 - pkg/engine/testdata/clone/configmap/Kptfile | 20 - .../testdata/clone/configmap/configmap.yaml | 21 - pkg/engine/testdata/context/expected/Kptfile | 6 - .../testdata/context/expected/bucket.yaml | 12 - .../context/expected/package-context.yaml | 9 - pkg/engine/testdata/context/input/Kptfile | 6 - pkg/engine/testdata/context/input/bucket.yaml | 12 - pkg/engine/testdata/init/testpkg/Kptfile | 13 - pkg/engine/testdata/init/testpkg/README.md | 21 - .../init/testpkg/package-context.yaml | 8 - pkg/engine/testdata/replace/Kptfile | 23 - pkg/engine/testdata/replace/README.md | 1 - pkg/engine/testdata/replace/bucket.yaml | 18 - .../testdata/simple-render/expected.txt | 27 - .../simple-render/simple-bucket/Kptfile | 29 - .../simple-render/simple-bucket/bucket.yaml | 26 - pkg/engine/testdata/update/local/Kptfile | 27 - pkg/engine/testdata/update/local/README.md | 21 - .../testdata/update/local/namespace.yaml | 5 - .../update/local/package-context.yaml | 8 - .../testdata/update/local/resourcequota.yaml | 9 - .../testdata/update/local/rolebinding.yaml | 13 - .../update/local/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/original/Kptfile | 14 - pkg/engine/testdata/update/original/README.md | 21 - .../testdata/update/original/namespace.yaml | 5 - .../update/original/package-context.yaml | 8 - .../update/original/resourcequota.yaml | 9 - .../testdata/update/original/rolebinding.yaml | 13 - .../update/original/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/updated/Kptfile | 27 - pkg/engine/testdata/update/updated/README.md | 21 - .../testdata/update/updated/namespace.yaml | 5 - .../update/updated/package-context.yaml | 8 - .../update/updated/resourcequota.yaml | 9 - .../testdata/update/updated/rolebinding.yaml | 13 - .../update/updated/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/upstream/Kptfile | 14 - pkg/engine/testdata/update/upstream/README.md | 21 - .../testdata/update/upstream/namespace.yaml | 5 - .../update/upstream/package-context.yaml | 8 - .../update/upstream/resourcequota.yaml | 9 - .../testdata/update/upstream/rolebinding.yaml | 13 - .../update/upstream/update-rolebinding.yaml | 20 - pkg/engine/watchermanager.go | 7 +- pkg/git/git.go | 6 +- pkg/git/package.go | 10 + pkg/oci/oci.go | 14 + pkg/registry/porch/package.go | 8 +- pkg/registry/porch/packagecommon.go | 19 +- pkg/registry/porch/packagerevision.go | 6 +- .../porch/packagerevisionresources.go | 7 +- pkg/registry/porch/reference.go | 8 +- pkg/registry/porch/watch.go | 15 +- pkg/registry/porch/watch_test.go | 10 +- pkg/{engine => repository}/environment.go | 7 +- .../fake/packagerevision.go | 45 +- pkg/{engine => repository}/fake/repository.go | 4 + pkg/repository/package.go | 153 +++ pkg/repository/repository.go | 7 + pkg/{engine => repository}/testing.go | 10 +- pkg/{engine => repository}/update.go | 33 +- pkg/{engine => repository}/update_test.go | 4 +- pkg/{engine => task}/builtin.go | 2 +- pkg/{engine => task}/builtin_test.go | 9 +- pkg/{engine => task}/clone.go | 21 +- pkg/{engine => task}/clone_test.go | 2 +- pkg/{engine => task}/edit.go | 12 +- pkg/{engine => task}/edit_test.go | 8 +- pkg/{engine => task}/eval.go | 2 +- pkg/{engine => task}/init.go | 2 +- pkg/{engine => task}/init_test.go | 2 +- pkg/{engine => task}/kio.go | 2 +- pkg/{engine => task}/mergekey.go | 2 +- .../engine_test.go => task/patch_test.go} | 14 +- pkg/{engine => task}/patchgen.go | 2 +- pkg/{engine => task}/patchgen_test.go | 2 +- pkg/{engine => task}/render.go | 2 +- pkg/{engine => task}/render_test.go | 2 +- pkg/{engine => task}/replace_test.go | 4 +- pkg/task/taskhandler.go | 675 ++++++++++++ pkg/util/util.go | 9 + 97 files changed, 1190 insertions(+), 1850 deletions(-) delete mode 100644 pkg/engine/doc.go delete mode 100644 pkg/engine/package.go delete mode 100644 pkg/engine/testdata/clone/bucket/Kptfile delete mode 100644 pkg/engine/testdata/clone/bucket/bucket.yaml delete mode 100644 pkg/engine/testdata/clone/configmap/Kptfile delete mode 100644 pkg/engine/testdata/clone/configmap/configmap.yaml delete mode 100644 pkg/engine/testdata/context/expected/Kptfile delete mode 100644 pkg/engine/testdata/context/expected/bucket.yaml delete mode 100644 pkg/engine/testdata/context/expected/package-context.yaml delete mode 100644 pkg/engine/testdata/context/input/Kptfile delete mode 100644 pkg/engine/testdata/context/input/bucket.yaml delete mode 100644 pkg/engine/testdata/init/testpkg/Kptfile delete mode 100644 pkg/engine/testdata/init/testpkg/README.md delete mode 100644 pkg/engine/testdata/init/testpkg/package-context.yaml delete mode 100644 pkg/engine/testdata/replace/Kptfile delete mode 100644 pkg/engine/testdata/replace/README.md delete mode 100644 pkg/engine/testdata/replace/bucket.yaml delete mode 100644 pkg/engine/testdata/simple-render/expected.txt delete mode 100644 pkg/engine/testdata/simple-render/simple-bucket/Kptfile delete mode 100644 pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml delete mode 100644 pkg/engine/testdata/update/local/Kptfile delete mode 100644 pkg/engine/testdata/update/local/README.md delete mode 100644 pkg/engine/testdata/update/local/namespace.yaml delete mode 100644 pkg/engine/testdata/update/local/package-context.yaml delete mode 100644 pkg/engine/testdata/update/local/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/local/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/local/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/original/Kptfile delete mode 100644 pkg/engine/testdata/update/original/README.md delete mode 100644 pkg/engine/testdata/update/original/namespace.yaml delete mode 100644 pkg/engine/testdata/update/original/package-context.yaml delete mode 100644 pkg/engine/testdata/update/original/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/original/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/original/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/updated/Kptfile delete mode 100644 pkg/engine/testdata/update/updated/README.md delete mode 100644 pkg/engine/testdata/update/updated/namespace.yaml delete mode 100644 pkg/engine/testdata/update/updated/package-context.yaml delete mode 100644 pkg/engine/testdata/update/updated/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/updated/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/updated/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/upstream/Kptfile delete mode 100644 pkg/engine/testdata/update/upstream/README.md delete mode 100644 pkg/engine/testdata/update/upstream/namespace.yaml delete mode 100644 pkg/engine/testdata/update/upstream/package-context.yaml delete mode 100644 pkg/engine/testdata/update/upstream/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/upstream/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/upstream/update-rolebinding.yaml rename pkg/{engine => repository}/environment.go (80%) rename pkg/{engine => repository}/fake/packagerevision.go (53%) rename pkg/{engine => repository}/fake/repository.go (97%) create mode 100644 pkg/repository/package.go rename pkg/{engine => repository}/testing.go (86%) rename pkg/{engine => repository}/update.go (78%) rename pkg/{engine => repository}/update_test.go (97%) rename pkg/{engine => task}/builtin.go (99%) rename pkg/{engine => task}/builtin_test.go (87%) rename pkg/{engine => task}/clone.go (93%) rename pkg/{engine => task}/clone_test.go (99%) rename pkg/{engine => task}/edit.go (90%) rename pkg/{engine => task}/edit_test.go (94%) rename pkg/{engine => task}/eval.go (99%) rename pkg/{engine => task}/init.go (99%) rename pkg/{engine => task}/init_test.go (99%) rename pkg/{engine => task}/kio.go (99%) rename pkg/{engine => task}/mergekey.go (99%) rename pkg/{engine/engine_test.go => task/patch_test.go} (96%) rename pkg/{engine => task}/patchgen.go (99%) rename pkg/{engine => task}/patchgen_test.go (99%) rename pkg/{engine => task}/render.go (99%) rename pkg/{engine => task}/render_test.go (99%) rename pkg/{engine => task}/replace_test.go (96%) create mode 100644 pkg/task/taskhandler.go diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 84e9b97c..6b9918f5 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -8,24 +8,6 @@ import ( ) type Cache interface { - OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (CachedRepository, error) + OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) CloseRepository(repositorySpec *configapi.Repository, allRepos []configapi.Repository) error } - -type CachedRepository interface { - repository.Repository - RefreshCache(ctx context.Context) error -} - -type CachedPackageRevision interface { - repository.PackageRevision -} - -type CachedPackageDraft interface { - repository.PackageDraft -} - -// Remove? -type CachedPackage interface { - repository.Package -} diff --git a/pkg/cache/fake/objectnotifier.go b/pkg/cache/fake/objectnotifier.go index e955a444..f9bde5aa 100644 --- a/pkg/cache/fake/objectnotifier.go +++ b/pkg/cache/fake/objectnotifier.go @@ -15,13 +15,12 @@ package fake import ( - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/watch" ) type ObjectNotifier struct{} -func (o *ObjectNotifier) NotifyPackageRevisionChange(watch.EventType, repository.PackageRevision, meta.PackageRevisionMeta) int { +func (o *ObjectNotifier) NotifyPackageRevisionChange(watch.EventType, repository.PackageRevision) int { return 0 } diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index 1961c41d..3cd53330 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -58,7 +58,7 @@ type Cache struct { var _ cache.Cache = &Cache{} type objectNotifier interface { - NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int + NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) int } type CacheOptions struct { @@ -105,7 +105,7 @@ func getCacheKey(repositorySpec *configapi.Repository) (string, error) { } } -func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (cache.CachedRepository, error) { +func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) { ctx, span := tracer.Start(ctx, "Cache::OpenRepository", trace.WithAttributes()) defer span.End() diff --git a/pkg/cache/memory/cache_test.go b/pkg/cache/memory/cache_test.go index f7baf304..de2fbc97 100644 --- a/pkg/cache/memory/cache_test.go +++ b/pkg/cache/memory/cache_test.go @@ -25,7 +25,6 @@ import ( "github.com/google/go-cmp/cmp" api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" fakecache "github.com/nephio-project/porch/pkg/cache/fake" "github.com/nephio-project/porch/pkg/git" @@ -216,7 +215,7 @@ func TestDeletePublishedMain(t *testing.T) { } -func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) cache.CachedRepository { +func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) repository.Repository { t.Helper() tempdir := t.TempDir() diff --git a/pkg/cache/memory/draft.go b/pkg/cache/memory/draft.go index 1a9066a1..5e44b259 100644 --- a/pkg/cache/memory/draft.go +++ b/pkg/cache/memory/draft.go @@ -18,7 +18,6 @@ import ( "context" "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" ) @@ -29,7 +28,6 @@ type cachedDraft struct { } var _ repository.PackageDraft = &cachedDraft{} -var _ cache.CachedPackageDraft = &cachedDraft{} func (cd *cachedDraft) Close(ctx context.Context, version string) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cachedDraft::Close", trace.WithAttributes()) diff --git a/pkg/cache/memory/package.go b/pkg/cache/memory/package.go index 0699583b..f8528bcc 100644 --- a/pkg/cache/memory/package.go +++ b/pkg/cache/memory/package.go @@ -15,7 +15,6 @@ package memory import ( - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" ) @@ -26,7 +25,6 @@ import ( // between Git and OCI. var _ repository.Package = &cachedPackage{} -var _ cache.CachedPackage = &cachedPackage{} type cachedPackage struct { repository.Package diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index 7f0b468d..aff6a6ba 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -17,8 +17,7 @@ package memory import ( "context" - "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" + api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/pkg/repository" ) @@ -29,26 +28,60 @@ import ( // between Git and OCI. var _ repository.PackageRevision = &cachedPackageRevision{} -var _ cache.CachedPackageRevision = &cachedPackageRevision{} type cachedPackageRevision struct { repository.PackageRevision isLatestRevision bool } -func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*v1alpha1.PackageRevision, error) { - rev, err := c.PackageRevision.GetPackageRevision(ctx) +func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*api.PackageRevision, error) { + apiPR, err := c.PackageRevision.GetPackageRevision(ctx) if err != nil { return nil, err } + + apiPR.Annotations = c.GetMeta().Annotations + apiPR.Finalizers = c.GetMeta().Finalizers + apiPR.OwnerReferences = c.GetMeta().OwnerReferences + apiPR.DeletionTimestamp = c.GetMeta().DeletionTimestamp + apiPR.Labels = c.GetMeta().Labels + if c.isLatestRevision { // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(rev.Labels)) - for k, v := range rev.Labels { + labels := make(map[string]string, len(apiPR.Labels)) + for k, v := range apiPR.Labels { + labels[k] = v + } + labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue + apiPR.Labels = labels + } + + return apiPR, nil +} + +func (c *cachedPackageRevision) GetCachedPackageRevision(ctx context.Context) (*api.PackageRevision, error) { + repoPkgRev, err := c.GetPackageRevision(ctx) + if err != nil { + return nil, err + } + var isLatest bool + if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { + isLatest = true + } + repoPkgRev.Labels = c.GetMeta().Labels + if isLatest { + // copy the labels in case the cached object is being read by another go routine + labels := make(map[string]string, len(repoPkgRev.Labels)) + for k, v := range repoPkgRev.Labels { labels[k] = v } - labels[v1alpha1.LatestPackageRevisionKey] = v1alpha1.LatestPackageRevisionValue - rev.Labels = labels + labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue + repoPkgRev.Labels = labels } - return rev, nil + repoPkgRev.Annotations = c.GetMeta().Annotations + repoPkgRev.Finalizers = c.GetMeta().Finalizers + repoPkgRev.OwnerReferences = c.GetMeta().OwnerReferences + repoPkgRev.DeletionTimestamp = c.GetMeta().DeletionTimestamp + + return repoPkgRev, nil } diff --git a/pkg/cache/memory/repository.go b/pkg/cache/memory/repository.go index b3874e0d..a7584a7c 100644 --- a/pkg/cache/memory/repository.go +++ b/pkg/cache/memory/repository.go @@ -22,13 +22,12 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/git" "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" - apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" @@ -43,7 +42,6 @@ var tracer = otel.Tracer("cache") // between Git and OCI. var _ repository.Repository = &cachedRepository{} -var _ cache.CachedRepository = &cachedRepository{} type cachedRepository struct { id string @@ -85,7 +83,7 @@ func newRepository(id string, repoSpec *configapi.Repository, repo repository.Re return r } -func (r *cachedRepository) RefreshCache(ctx context.Context) error { +func (r *cachedRepository) Refresh(ctx context.Context) error { _, _, err := r.refreshAllCachedPackages(ctx) @@ -261,7 +259,7 @@ func (r *cachedRepository) createMainPackageRevision(ctx context.Context, update // Create the package if it doesn't exist _, err := r.metadataStore.Get(ctx, pkgRevMetaNN) - if apierrors.IsNotFound(err) { + if errors.IsNotFound(err) { pkgRevMeta := meta.PackageRevisionMeta{ Name: updatedMain.KubeObjectName(), Namespace: updatedMain.KubeObjectNamespace(), @@ -346,19 +344,15 @@ func (r *cachedRepository) Close() error { // the repository, so we have to just delete the PackageRevision regardless of any // finalizers. klog.Infof("repo %s: deleting packagerev %s/%s because repository is closed", r.id, nn.Namespace, nn.Name) - pkgRevMeta, err := r.metadataStore.Delete(context.TODO(), nn, true) + _, err := r.metadataStore.Delete(context.TODO(), nn, true) if err != nil { // There isn't much use in returning an error here, so we just log it // and create a PackageRevisionMeta with just name and namespace. This // makes sure that the Delete event is sent. klog.Warningf("repo %s: error deleting packagerev for %s: %v", r.id, nn.Name, err) - pkgRevMeta = meta.PackageRevisionMeta{ - Name: nn.Name, - Namespace: nn.Namespace, - } } klog.Infof("repo %s: successfully deleted packagerev %s/%s", r.id, nn.Namespace, nn.Name) - sent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, pr, pkgRevMeta) + sent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, pr) } klog.Infof("repo %s: sent %d notifications for %d package revisions during close", r.id, sent, len(r.cachedPackageRevisions)) return r.repo.Close() @@ -474,7 +468,7 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re Name: prm.Name, Namespace: prm.Namespace, }, true); err != nil { - if !apierrors.IsNotFound(err) { + if !errors.IsNotFound(err) { // This will be retried the next time the sync runs. klog.Warningf("repo %s: unable to delete PackageRev CR for %s/%s: %v", r.id, prm.Name, prm.Namespace, err) @@ -488,15 +482,11 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re modSent := 0 for kname, newPackage := range newPackageRevisionNames { oldPackage := oldPackageRevisionNames[kname] - metaPackage, found := existingPkgRevCRsMap[newPackage.KubeObjectName()] - if !found { - klog.Warningf("no PackageRev CR found for PackageRevision %s", newPackage.KubeObjectName()) - } if oldPackage == nil { - addSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Added, newPackage, metaPackage) + addSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Added, newPackage) } else { if oldPackage.ResourceVersion() != newPackage.ResourceVersion() { - modSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Modified, newPackage, metaPackage) + modSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Modified, newPackage) } } } @@ -529,17 +519,7 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re } klog.Infof("repo %s: deleting PackageRev %s/%s because PackageRevision was removed from SoT", r.id, nn.Namespace, nn.Name) - metaPackage, err := r.metadataStore.Delete(ctx, nn, true) - if err != nil { - if !apierrors.IsNotFound(err) { - klog.Warningf("repo %s: error deleting PkgRevMeta %s: %v", r.id, nn, err) - } - metaPackage = meta.PackageRevisionMeta{ - Name: nn.Name, - Namespace: nn.Namespace, - } - } - delSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, oldPackage, metaPackage) + delSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, oldPackage) } } klog.Infof("repo %s: addSent %d, modSent %d, delSent for %d old and %d new repo packages", r.id, addSent, modSent, len(oldPackageRevisionNames), len(newPackageRevisionNames)) diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index f703e59c..cdb3ca1c 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -57,6 +57,7 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac latest[currentKey.Package] = current } } + // Mark the winners as latest for _, v := range latest { v.isLatestRevision = true diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go deleted file mode 100644 index c040bddc..00000000 --- a/pkg/engine/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2022 The kpt and Nephio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Implementation of package orchestration engine -// The engine is independent of k8s runtime (k8s apiserver integration is in apiserver module) -package engine diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index ac430479..84d1f14f 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -15,7 +15,6 @@ package engine import ( - "bytes" "context" "errors" "fmt" @@ -24,29 +23,16 @@ import ( api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/internal/kpt/builtins" - "github.com/nephio-project/porch/internal/kpt/fnruntime" cache "github.com/nephio-project/porch/pkg/cache" - "github.com/nephio-project/porch/pkg/kpt" - kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" - "github.com/nephio-project/porch/pkg/kpt/fn" "github.com/nephio-project/porch/pkg/meta" - "github.com/nephio-project/porch/pkg/objects" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/task" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/kustomize/kyaml/comments" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" ) var tracer = otel.Tracer("engine") @@ -59,79 +45,17 @@ type CaDEngine interface { // ObjectCache() is a cache of all our objects. ObjectCache() WatcherManager - UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevisionResources) (*PackageRevision, *api.RenderStatus, error) + UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage repository.PackageRevision, old, new *api.PackageRevisionResources) (repository.PackageRevision, *api.RenderStatus, error) - ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]*PackageRevision, error) - CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) - UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) - DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *PackageRevision) error + ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]repository.PackageRevision, error) + CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) + UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage repository.PackageRevision, old, new *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) + DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj repository.PackageRevision) error - ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]*Package, error) - CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (*Package, error) - UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package, old, new *api.PorchPackage) (*Package, error) - DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *Package) error -} - -type Package struct { - repoPackage repository.Package -} - -func (p *Package) GetPackage() *api.PorchPackage { - return p.repoPackage.GetPackage() -} - -func (p *Package) KubeObjectName() string { - return p.repoPackage.KubeObjectName() -} - -// TODO: This is a bit awkward, and we should see if there is a way to avoid -// having to expose this function. Any functionality that requires creating new -// engine.PackageRevision resources should be in the engine package. -func ToPackageRevision(pkgRev repository.PackageRevision, pkgRevMeta meta.PackageRevisionMeta) *PackageRevision { - return &PackageRevision{ - repoPackageRevision: pkgRev, - packageRevisionMeta: pkgRevMeta, - } -} - -type PackageRevision struct { - repoPackageRevision repository.PackageRevision - packageRevisionMeta meta.PackageRevisionMeta -} - -func (p *PackageRevision) GetPackageRevision(ctx context.Context) (*api.PackageRevision, error) { - repoPkgRev, err := p.repoPackageRevision.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - var isLatest bool - if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { - isLatest = true - } - repoPkgRev.Labels = p.packageRevisionMeta.Labels - if isLatest { - // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(repoPkgRev.Labels)) - for k, v := range repoPkgRev.Labels { - labels[k] = v - } - labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue - repoPkgRev.Labels = labels - } - repoPkgRev.Annotations = p.packageRevisionMeta.Annotations - repoPkgRev.Finalizers = p.packageRevisionMeta.Finalizers - repoPkgRev.OwnerReferences = p.packageRevisionMeta.OwnerReferences - repoPkgRev.DeletionTimestamp = p.packageRevisionMeta.DeletionTimestamp - - return repoPkgRev, nil -} - -func (p *PackageRevision) KubeObjectName() string { - return p.repoPackageRevision.KubeObjectName() -} - -func (p *PackageRevision) GetResources(ctx context.Context) (*api.PackageRevisionResources, error) { - return p.repoPackageRevision.GetResources(ctx) + ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]repository.Package, error) + CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (repository.Package, error) + UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage repository.Package, old, new *api.PorchPackage) (repository.Package, error) + DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, obj repository.Package) error } func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { @@ -147,23 +71,14 @@ func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { type cadEngine struct { cache cache.Cache - // runnerOptionsResolver returns the RunnerOptions for function execution in the specified namespace. - runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions - - runtime fn.FunctionRuntime - credentialResolver repository.CredentialResolver - referenceResolver ReferenceResolver - userInfoProvider repository.UserInfoProvider - metadataStore meta.MetadataStore - watcherManager *watcherManager + userInfoProvider repository.UserInfoProvider + metadataStore meta.MetadataStore + watcherManager *watcherManager + taskHandler task.TaskHandler } var _ CaDEngine = &cadEngine{} -type mutation interface { - Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) -} - // ObjectCache is a cache of all our objects. func (cad *cadEngine) ObjectCache() WatcherManager { return cad.watcherManager @@ -176,7 +91,7 @@ func (cad *cadEngine) OpenRepository(ctx context.Context, repositorySpec *config return cad.cache.OpenRepository(ctx, repositorySpec) } -func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]*PackageRevision, error) { +func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::ListPackageRevisions", trace.WithAttributes()) defer span.End() @@ -189,7 +104,7 @@ func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec * return nil, err } - var packageRevisions []*PackageRevision + var packageRevisions []repository.PackageRevision for _, pr := range pkgRevs { pkgRevMeta, err := cad.metadataStore.Get(ctx, types.NamespacedName{ Name: pr.KubeObjectName(), @@ -203,63 +118,17 @@ func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec * } return nil, err } - packageRevisions = append(packageRevisions, &PackageRevision{ - repoPackageRevision: pr, - packageRevisionMeta: pkgRevMeta, - }) + pr.SetMeta(pkgRevMeta) + packageRevisions = append(packageRevisions, pr) } return packageRevisions, nil } -func buildPackageConfig(ctx context.Context, obj *api.PackageRevision, parent *PackageRevision) (*builtins.PackageConfig, error) { - config := &builtins.PackageConfig{} - - parentPath := "" - - var parentConfig *unstructured.Unstructured - if parent != nil { - parentObj, err := parent.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - parentPath = parentObj.Spec.PackageName - - resources, err := parent.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("error getting resources from parent package %q: %w", parentObj.Name, err) - } - configMapObj, err := ExtractContextConfigMap(resources.Spec.Resources) - if err != nil { - return nil, fmt.Errorf("error getting configuration from parent package %q: %w", parentObj.Name, err) - } - parentConfig = configMapObj - - if parentConfig != nil { - // TODO: Should we support kinds other than configmaps? - var parentConfigMap corev1.ConfigMap - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(parentConfig.Object, &parentConfigMap); err != nil { - return nil, fmt.Errorf("error parsing ConfigMap from parent configuration: %w", err) - } - if s := parentConfigMap.Data[builtins.ConfigKeyPackagePath]; s != "" { - parentPath = s + "/" + parentPath - } - } - } - - if parentPath == "" { - config.PackagePath = obj.Spec.PackageName - } else { - config.PackagePath = parentPath + "/" + obj.Spec.PackageName - } - - return config, nil -} - -func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) { +func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::CreatePackageRevision", trace.WithAttributes()) defer span.End() - packageConfig, err := buildPackageConfig(ctx, obj, parent) + packageConfig, err := repository.BuildPackageConfig(ctx, obj, parent) if err != nil { return nil, err } @@ -302,7 +171,7 @@ func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj * return nil, err } - if err := cad.applyTasks(ctx, draft, repositoryObj, obj, packageConfig); err != nil { + if err := cad.taskHandler.ApplyTasks(ctx, draft, repositoryObj, obj, packageConfig); err != nil { return nil, err } @@ -327,12 +196,10 @@ func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj * if err != nil { return nil, err } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Added, repoPkgRev, pkgRevMeta) + repoPkgRev.SetMeta(pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Added, repoPkgRev) klog.Infof("engine: sent %d for new PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return &PackageRevision{ - repoPackageRevision: repoPkgRev, - packageRevisionMeta: pkgRevMeta, - }, nil + return repoPkgRev, nil } // The workspaceName must be unique, because it used to generate the package revision's metadata.name. @@ -347,141 +214,7 @@ func ensureUniqueWorkspaceName(obj *api.PackageRevision, existingRevs []reposito return nil } -func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { - for _, tt := range oneOf { - if taskType == tt { - return true - } - } - return false -} - -func (cad *cadEngine) applyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { - var mutations []mutation - - // Unless first task is Init or Clone, insert Init to create an empty package. - tasks := obj.Spec.Tasks - if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { - mutations = append(mutations, &initPackageMutation{ - name: obj.Spec.PackageName, - task: &api.Task{ - Init: &api.PackageInitTaskSpec{ - Subpackage: "", - Description: fmt.Sprintf("%s description", obj.Spec.PackageName), - }, - }, - }) - } - - for i := range tasks { - task := &tasks[i] - mutation, err := cad.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) - if err != nil { - return err - } - mutations = append(mutations, mutation) - } - - // Render package after creation. - mutations = cad.conditionalAddRender(obj, mutations) - - baseResources := repository.PackageResources{} - if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { - return err - } - - return nil -} - -type RepositoryOpener interface { - OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) -} - -func (cad *cadEngine) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { - switch task.Type { - case api.TaskTypeInit: - if task.Init == nil { - return nil, fmt.Errorf("init not set for task of type %q", task.Type) - } - return &initPackageMutation{ - name: obj.Spec.PackageName, - task: task, - }, nil - case api.TaskTypeClone: - if task.Clone == nil { - return nil, fmt.Errorf("clone not set for task of type %q", task.Type) - } - return &clonePackageMutation{ - task: task, - namespace: obj.Namespace, - name: obj.Spec.PackageName, - isDeployment: isDeployment, - repoOpener: cad, - credentialResolver: cad.credentialResolver, - referenceResolver: cad.referenceResolver, - packageConfig: packageConfig, - }, nil - - case api.TaskTypeUpdate: - if task.Update == nil { - return nil, fmt.Errorf("update not set for task of type %q", task.Type) - } - cloneTask := findCloneTask(obj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) - } - return &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: task, - namespace: obj.Namespace, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - pkgName: obj.Spec.PackageName, - }, nil - - case api.TaskTypePatch: - return buildPatchMutation(ctx, task) - - case api.TaskTypeEdit: - if task.Edit == nil { - return nil, fmt.Errorf("edit not set for task of type %q", task.Type) - } - return &editPackageMutation{ - task: task, - namespace: obj.Namespace, - packageName: obj.Spec.PackageName, - repositoryName: obj.Spec.RepositoryName, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - }, nil - - case api.TaskTypeEval: - if task.Eval == nil { - return nil, fmt.Errorf("eval not set for task of type %q", task.Type) - } - // TODO: We should find a different way to do this. Probably a separate - // task for render. - if task.Eval.Image == "render" { - runnerOptions := cad.runnerOptionsResolver(obj.Namespace) - return &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }, nil - } else { - runnerOptions := cad.runnerOptionsResolver(obj.Namespace) - return &evalFunctionMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - task: task, - }, nil - } - - default: - return nil, fmt.Errorf("task of type %q not supported", task.Type) - } -} - -func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage *PackageRevision, oldObj, newObj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) { +func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, repoPr repository.PackageRevision, oldObj, newObj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::UpdatePackageRevision", trace.WithAttributes()) defer span.End() @@ -491,7 +224,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, } if newRV != oldObj.GetResourceVersion() { - return nil, apierrors.NewConflict(api.Resource("packagerevisions"), oldObj.GetName(), errors.New(OptimisticLockErrorMsg)) + return nil, apierrors.NewConflict(api.Resource("packagerevisions"), oldObj.GetName(), fmt.Errorf(OptimisticLockErrorMsg)) } repo, err := cad.cache.OpenRepository(ctx, repositoryObj) @@ -501,7 +234,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, // Check if the PackageRevision is in the terminating state and // and this request removes the last finalizer. - repoPkgRev := oldPackage.repoPackageRevision + repoPkgRev := repoPr pkgRevMetaNN := types.NamespacedName{ Name: repoPkgRev.KubeObjectName(), Namespace: repoPkgRev.KubeObjectNamespace(), @@ -510,145 +243,62 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, if err != nil { return nil, err } + repoPkgRev.SetMeta(pkgRevMeta) // If this is in the terminating state and we are removing the last finalizer, // we delete the resource instead of updating it. if pkgRevMeta.DeletionTimestamp != nil && len(newObj.Finalizers) == 0 { - if err := cad.deletePackageRevision(ctx, repo, repoPkgRev, pkgRevMeta); err != nil { + if err := cad.deletePackageRevision(ctx, repo, repoPkgRev); err != nil { return nil, err } - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil + return repoPkgRev, nil } // Validate package lifecycle. Can only update a draft. switch lifecycle := oldObj.Spec.Lifecycle; lifecycle { - default: - return nil, fmt.Errorf("invalid original lifecycle value: %q", lifecycle) + case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed: // Draft or proposed can be updated. + case api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: // Only metadata (currently labels and annotations) and lifecycle can be updated for published packages. if oldObj.Spec.Lifecycle != newObj.Spec.Lifecycle { - if err := oldPackage.repoPackageRevision.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { + if err := repoPr.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { return nil, err } } - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) if err != nil { return nil, err } - - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) klog.Infof("engine: sent %d for updated PackageRevision metadata %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil - } - switch lifecycle := newObj.Spec.Lifecycle; lifecycle { - default: - return nil, fmt.Errorf("invalid desired lifecycle value: %q", lifecycle) - case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed, api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: - // These values are ok - } - - if isRecloneAndReplay(oldObj, newObj) { - packageConfig, err := buildPackageConfig(ctx, newObj, parent) - if err != nil { - return nil, err - } - repoPkgRev, err := cad.recloneAndReplay(ctx, version, repo, repositoryObj, newObj, packageConfig) - if err != nil { - return nil, err - } - - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) - if err != nil { - return nil, err - } + return repoPkgRev, nil - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) - klog.Infof("engine: sent %d for reclone and replay PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil - } - - var mutations []mutation - if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { - return nil, fmt.Errorf("removing tasks is not yet supported") - } - for i := range oldObj.Spec.Tasks { - oldTask := &oldObj.Spec.Tasks[i] - newTask := &newObj.Spec.Tasks[i] - if oldTask.Type != newTask.Type { - return nil, fmt.Errorf("changing task types is not yet supported") - } + default: + return nil, fmt.Errorf("invalid original lifecycle value: %q", lifecycle) } - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { - return nil, fmt.Errorf("can only append one task at a time") - } - newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] - if newTask.Type != api.TaskTypeUpdate { - return nil, fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) - } - if newTask.Update == nil { - return nil, fmt.Errorf("update not set for updateTask of type %q", newTask.Type) - } + switch lifecycle := newObj.Spec.Lifecycle; lifecycle { - cloneTask := findCloneTask(oldObj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) - } + case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed, api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: + // These values are ok - mutation := &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: &newTask, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - namespace: repositoryObj.Namespace, - pkgName: oldObj.GetName(), - } - mutations = append(mutations, mutation) + default: + return nil, fmt.Errorf("invalid desired lifecycle value: %q", lifecycle) } - // Re-render if we are making changes. - mutations = cad.conditionalAddRender(newObj, mutations) - - draft, err := repo.UpdatePackageRevision(ctx, oldPackage.repoPackageRevision) - if err != nil { - return nil, err + if isRecloneAndReplay(oldObj, newObj) { + return cad.RecloneAndReplay(ctx, parent, version, repo, repositoryObj, newObj) } - // If any of the fields in the API that are projections from the Kptfile - // must be updated in the Kptfile as well. - kfPatchTask, created, err := createKptfilePatchTask(ctx, oldPackage.repoPackageRevision, newObj) + // Do we need to clean up this draft later? + draft, err := repo.UpdatePackageRevision(ctx, repoPr) if err != nil { return nil, err } - if created { - kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) - if err != nil { - return nil, err - } - mutations = append(mutations, kfPatchMutation) - } - - // Re-render if we are making changes. - mutations = cad.conditionalAddRender(newObj, mutations) - // TODO: Handle the case if alongside lifecycle change, tasks are changed too. - // Update package contents only if the package is in draft state - if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { - apiResources, err := oldPackage.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: apiResources.Spec.Resources, - } - - if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { - return nil, err - } - } + cad.taskHandler.DoPRMutations(ctx, repositoryObj.Namespace, repoPr, oldObj, newObj, draft) if err := draft.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { return nil, err @@ -660,18 +310,18 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, return nil, err } - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) if err != nil { return nil, err } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) klog.Infof("engine: sent %d for updated PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil + return repoPkgRev, nil } -func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repository.PackageRevision, apiPkgRev *api.PackageRevision) (meta.PackageRevisionMeta, error) { +func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repository.PackageRevision, apiPkgRev *api.PackageRevision) error { pkgRevMeta := meta.PackageRevisionMeta{ Name: repoPkgRev.KubeObjectName(), Namespace: repoPkgRev.KubeObjectNamespace(), @@ -680,118 +330,18 @@ func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repositor Finalizers: apiPkgRev.Finalizers, OwnerReferences: apiPkgRev.OwnerReferences, } - return cad.metadataStore.Update(ctx, pkgRevMeta) -} -func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { - kf, err := oldPackage.GetKptfile(ctx) - if err != nil { - return nil, false, err - } + repoPkgRev.SetMeta(pkgRevMeta) - var orgKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - orgKfString = buf.String() - } - - var readinessGates []kptfile.ReadinessGate - for _, rg := range newObj.Spec.ReadinessGates { - readinessGates = append(readinessGates, kptfile.ReadinessGate{ - ConditionType: rg.ConditionType, - }) - } - - var conditions []kptfile.Condition - for _, c := range newObj.Status.Conditions { - conditions = append(conditions, kptfile.Condition{ - Type: c.Type, - Status: convertStatusToKptfile(c.Status), - Reason: c.Reason, - Message: c.Message, - }) - } - - if kf.Info == nil && len(readinessGates) > 0 { - kf.Info = &kptfile.PackageInfo{} - } - if len(readinessGates) > 0 { - kf.Info.ReadinessGates = readinessGates - } - - if kf.Status == nil && len(conditions) > 0 { - kf.Status = &kptfile.Status{} - } - if len(conditions) > 0 { - kf.Status.Conditions = conditions - } - - var newKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - newKfString = buf.String() - } - patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) - if err != nil { - return nil, false, err - } - // If patch is empty, don't create a Task. - if patchSpec.Contents == "" { - return nil, false, nil - } - - return &api.Task{ - Type: api.TaskTypePatch, - Patch: &api.PackagePatchTaskSpec{ - Patches: []api.PatchSpec{ - patchSpec, - }, - }, - }, true, nil -} - -func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { - switch s { - case api.ConditionTrue: - return kptfile.ConditionTrue - case api.ConditionFalse: - return kptfile.ConditionFalse - case api.ConditionUnknown: - return kptfile.ConditionUnknown - default: - panic(fmt.Errorf("unknown condition status: %v", s)) - } -} - -// conditionalAddRender adds a render mutation to the end of the mutations slice if the last -// entry is not already a render mutation. -func (cad *cadEngine) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { - if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { - return mutations + if storedPkgRevMeta, err := cad.metadataStore.Update(ctx, pkgRevMeta); err == nil { + repoPkgRev.SetMeta(storedPkgRevMeta) + return nil + } else { + return err } - - runnerOptions := cad.runnerOptionsResolver(subject.GetNamespace()) - - return append(mutations, &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }) } -func isRenderMutation(m mutation) bool { - _, isRender := m.(*renderPackageMutation) - return isRender -} - -func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision) error { +func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, pr2Del repository.PackageRevision) error { ctx, span := tracer.Start(ctx, "cadEngine::DeletePackageRevision", trace.WithAttributes()) defer span.End() @@ -807,8 +357,8 @@ func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj * // But we only delete the PackageRevision from the repo once all finalizers // have been removed. namespacedName := types.NamespacedName{ - Name: oldPackage.repoPackageRevision.KubeObjectName(), - Namespace: oldPackage.repoPackageRevision.KubeObjectNamespace(), + Name: pr2Del.KubeObjectName(), + Namespace: pr2Del.KubeObjectNamespace(), } pkgRevMeta, err := cad.metadataStore.Delete(ctx, namespacedName, false) if err != nil { @@ -816,17 +366,17 @@ func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj * } if len(pkgRevMeta.Finalizers) > 0 { - klog.Infof("PackageRevision %s deleted, but still have finalizers: %s", oldPackage.KubeObjectName(), strings.Join(pkgRevMeta.Finalizers, ",")) - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, oldPackage.repoPackageRevision, oldPackage.packageRevisionMeta) - klog.Infof("engine: sent %d modified for deleted PackageRevision %s/%s with finalizers", sent, oldPackage.repoPackageRevision.KubeObjectNamespace(), oldPackage.KubeObjectName()) + klog.Infof("PackageRevision %s deleted, but still have finalizers: %s", pr2Del.KubeObjectName(), strings.Join(pkgRevMeta.Finalizers, ",")) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, pr2Del) + klog.Infof("engine: sent %d modified for deleted PackageRevision %s/%s with finalizers", sent, pr2Del.KubeObjectNamespace(), pr2Del.KubeObjectName()) return nil } - klog.Infof("PackageRevision %s deleted for real since no finalizers", oldPackage.KubeObjectName()) + klog.Infof("PackageRevision %s deleted for real since no finalizers", pr2Del.KubeObjectName()) - return cad.deletePackageRevision(ctx, repo, oldPackage.repoPackageRevision, oldPackage.packageRevisionMeta) + return cad.deletePackageRevision(ctx, repo, pr2Del) } -func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository.Repository, repoPkgRev repository.PackageRevision, pkgRevMeta meta.PackageRevisionMeta) error { +func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository.Repository, repoPkgRev repository.PackageRevision) error { ctx, span := tracer.Start(ctx, "cadEngine::deletePackageRevision", trace.WithAttributes()) defer span.End() @@ -835,8 +385,8 @@ func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository } nn := types.NamespacedName{ - Name: pkgRevMeta.Name, - Namespace: pkgRevMeta.Namespace, + Name: repoPkgRev.GetMeta().Name, + Namespace: repoPkgRev.GetMeta().Namespace, } if _, err := cad.metadataStore.Delete(ctx, nn, true); err != nil { // If this fails, the CR will be cleaned up by the background job. @@ -845,12 +395,12 @@ func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository } } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Deleted, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Deleted, repoPkgRev) klog.Infof("engine: sent %d for deleted PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) return nil } -func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]*Package, error) { +func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]repository.Package, error) { ctx, span := tracer.Start(ctx, "cadEngine::ListPackages", trace.WithAttributes()) defer span.End() @@ -863,17 +413,15 @@ func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configap if err != nil { return nil, err } - var packages []*Package + var packages []repository.Package for _, p := range pkgs { - packages = append(packages, &Package{ - repoPackage: p, - }) + packages = append(packages, p) } return packages, nil } -func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (*Package, error) { +func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (repository.Package, error) { ctx, span := tracer.Start(ctx, "cadEngine::CreatePackage", trace.WithAttributes()) defer span.End() @@ -886,21 +434,19 @@ func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configap return nil, err } - return &Package{ - repoPackage: pkg, - }, nil + return pkg, nil } -func (cad *cadEngine) UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package, oldObj, newObj *api.PorchPackage) (*Package, error) { +func (cad *cadEngine) UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, pkg2Update repository.Package, oldObj, newObj *api.PorchPackage) (repository.Package, error) { _, span := tracer.Start(ctx, "cadEngine::UpdatePackage", trace.WithAttributes()) defer span.End() // TODO - var pkg *Package + var pkg repository.Package return pkg, errors.New("updating packages is not yet supported") } -func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package) error { +func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, pkg2Del repository.Package) error { ctx, span := tracer.Start(ctx, "cadEngine::DeletePackage", trace.WithAttributes()) defer span.End() @@ -909,29 +455,29 @@ func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configap return err } - if err := repo.DeletePackage(ctx, oldPackage.repoPackage); err != nil { + if err := repo.DeletePackage(ctx, pkg2Del); err != nil { return err } return nil } -func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevisionResources) (*PackageRevision, *api.RenderStatus, error) { +func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, pr2Update repository.PackageRevision, oldRes, newRes *api.PackageRevisionResources) (repository.PackageRevision, *api.RenderStatus, error) { ctx, span := tracer.Start(ctx, "cadEngine::UpdatePackageResources", trace.WithAttributes()) defer span.End() - rev, err := oldPackage.repoPackageRevision.GetPackageRevision(ctx) + rev, err := pr2Update.GetPackageRevision(ctx) if err != nil { return nil, nil, err } - newRV := new.GetResourceVersion() + newRV := newRes.GetResourceVersion() if len(newRV) == 0 { return nil, nil, fmt.Errorf("resourceVersion must be specified for an update") } - if newRV != old.GetResourceVersion() { - return nil, nil, apierrors.NewConflict(api.Resource("packagerevisionresources"), old.GetName(), errors.New(OptimisticLockErrorMsg)) + if newRV != oldRes.GetResourceVersion() { + return nil, nil, apierrors.NewConflict(api.Resource("packagerevisionresources"), oldRes.GetName(), errors.New(OptimisticLockErrorMsg)) } // Validate package lifecycle. Can only update a draft. @@ -949,315 +495,19 @@ func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj if err != nil { return nil, nil, err } - draft, err := repo.UpdatePackageRevision(ctx, oldPackage.repoPackageRevision) + draft, err := repo.UpdatePackageRevision(ctx, pr2Update) if err != nil { return nil, nil, err } - runnerOptions := cad.runnerOptionsResolver(old.GetNamespace()) - - mutations := []mutation{ - &mutationReplaceResources{ - newResources: new, - oldResources: old, - }, - } - prevResources, err := oldPackage.repoPackageRevision.GetResources(ctx) - if err != nil { - return nil, nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: prevResources.Spec.Resources, - } - - appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) - if err != nil { - return nil, nil, err - } - - var renderStatus *api.RenderStatus - if len(appliedResources.Contents) > 0 { - // render the package - // Render failure will not fail the overall API operation. - // The render error and result is captured as part of renderStatus above - // and is returned in packageresourceresources API's status field. We continue with - // saving the non-rendered resources to avoid losing user's changes. - // and supress this err. - _, renderStatus, _ = applyResourceMutations(ctx, - draft, - appliedResources, - []mutation{&renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }}) - } else { - renderStatus = nil - } + renderStatus, err := cad.taskHandler.DoPRResourceMutations(ctx, pr2Update, draft, oldRes, newRes) // No lifecycle change when updating package resources; updates are done. repoPkgRev, err := draft.Close(ctx, "") if err != nil { return nil, renderStatus, err } - return &PackageRevision{ - repoPackageRevision: repoPkgRev, - }, renderStatus, nil -} - -// applyResourceMutations mutates the resources and returns the most recent renderResult. -func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { - var lastApplied mutation - for _, m := range mutations { - updatedResources, taskResult, err := m.Apply(ctx, baseResources) - if taskResult == nil && err == nil { - // a nil taskResult means nothing changed - continue - } - - var task *api.Task - if taskResult != nil { - task = taskResult.Task - } - if taskResult != nil && task.Type == api.TaskTypeEval { - renderStatus = taskResult.RenderStatus - } - if err != nil { - return updatedResources, renderStatus, err - } - - // if the last applied mutation was a render mutation, and so is this one, skip it - if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { - continue - } - lastApplied = m - - if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ - Spec: api.PackageRevisionResourcesSpec{ - Resources: updatedResources.Contents, - }, - }, task); err != nil { - return updatedResources, renderStatus, err - } - baseResources = updatedResources - applied = updatedResources - } - - return applied, renderStatus, nil -} - -type updatePackageMutation struct { - cloneTask *api.Task - updateTask *api.Task - repoOpener RepositoryOpener - referenceResolver ReferenceResolver - namespace string - pkgName string -} - -func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) - defer span.End() - - currUpstreamPkgRef, err := m.currUpstream() - if err != nil { - return repository.PackageResources{}, nil, err - } - - targetUpstream := m.updateTask.Update.Upstream - if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { - return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") - } - - originalResources, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, - }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", - m.pkgName, *currUpstreamPkgRef) - } - - upstreamRevision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, - }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) - } - upstreamResources, err := upstreamRevision.GetResources(ctx) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) - } - - klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", - m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) - - // May be have packageUpdater part of engine to make it easy for testing ? - updatedResources, err := (&defaultPackageUpdater{}).Update(ctx, - resources, - repository.PackageResources{ - Contents: originalResources.Spec.Resources, - }, - repository.PackageResources{ - Contents: upstreamResources.Spec.Resources, - }) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) - } - - newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) - } - if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) - } - - // ensure merge-key comment is added to newly added resources. - result, err := ensureMergeKey(ctx, updatedResources) - if err != nil { - klog.Infof("failed to add merge key comments: %v", err) - } - return result, &api.TaskResult{Task: m.updateTask}, nil -} - -// Currently assumption is that downstream packages will be forked from a porch package. -// As per current implementation, upstream package ref is stored in a new update task but this may -// change so the logic of figuring out current upstream will live in this function. -func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { - if m.cloneTask == nil || m.cloneTask.Clone == nil { - return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) - } - upstream := m.cloneTask.Clone.Upstream - if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { - return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) - } - return upstream.UpstreamRef, nil -} - -func findCloneTask(pr *api.PackageRevision) *api.Task { - if len(pr.Spec.Tasks) == 0 { - return nil - } - firstTask := pr.Spec.Tasks[0] - if firstTask.Type == api.TaskTypeClone { - return &firstTask - } - return nil -} - -type mutationReplaceResources struct { - newResources *api.PackageRevisionResources - oldResources *api.PackageRevisionResources -} - -func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) - defer span.End() - - patch := &api.PackagePatchTaskSpec{} - - old := resources.Contents - new, err := healConfig(old, m.newResources.Spec.Resources) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) - } - - for k, newV := range new { - oldV, ok := old[k] - // New config or changed config - if !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeCreateFile, - Contents: newV, - } - patch.Patches = append(patch.Patches, patchSpec) - } else if newV != oldV { - patchSpec, err := GeneratePatch(k, oldV, newV) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) - } - if patchSpec.Contents == "" { - continue - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - for k := range old { - // Deleted config - if _, ok := new[k]; !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeDeleteFile, - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - // If patch is empty, don't create a Task. - var taskResult *api.TaskResult - if len(patch.Patches) > 0 { - taskResult = &api.TaskResult{ - Task: &api.Task{ - Type: api.TaskTypePatch, - Patch: patch, - }, - } - } - return repository.PackageResources{Contents: new}, taskResult, nil -} - -func healConfig(old, new map[string]string) (map[string]string, error) { - // Copy comments from old config to new - oldResources, err := (&packageReader{ - input: repository.PackageResources{Contents: old}, - extra: map[string]string{}, - }).Read() - if err != nil { - return nil, fmt.Errorf("failed to read old packge resources: %w", err) - } - - var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { - for _, n := range r { - for _, original := range oldResources { - if n.GetNamespace() == original.GetNamespace() && - n.GetName() == original.GetName() && - n.GetApiVersion() == original.GetApiVersion() && - n.GetKind() == original.GetKind() { - comments.CopyComments(original, n) - } - } - } - return r, nil - } - - out := &packageWriter{ - output: repository.PackageResources{ - Contents: map[string]string{}, - }, - } - - extra := map[string]string{} - - if err := (kio.Pipeline{ - Inputs: []kio.Reader{&packageReader{ - input: repository.PackageResources{Contents: new}, - extra: extra, - }}, - Filters: []kio.Filter{filter}, - Outputs: []kio.Writer{out}, - ContinueOnEmptyResult: true, - }).Execute(); err != nil { - return nil, err - } - - healed := out.output.Contents - - for k, v := range extra { - healed[k] = v - } - - return healed, nil + return repoPkgRev, renderStatus, nil } // isRecloneAndReplay determines if an update should be handled using reclone-and-replay semantics. @@ -1282,10 +532,15 @@ func isRecloneAndReplay(oldObj, newObj *api.PackageRevision) bool { // recloneAndReplay performs an update by recloning the upstream package and replaying all tasks. // This is more like a git rebase operation than the "classic" kpt update algorithm, which is more like a git merge. -func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo repository.Repository, repositoryObj *configapi.Repository, newObj *api.PackageRevision, packageConfig *builtins.PackageConfig) (repository.PackageRevision, error) { +func (cad *cadEngine) RecloneAndReplay(ctx context.Context, parentPR repository.PackageRevision, version string, repo repository.Repository, repositoryObj *configapi.Repository, newObj *api.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::recloneAndReplay", trace.WithAttributes()) defer span.End() + packageConfig, err := repository.BuildPackageConfig(ctx, newObj, parentPR) + if err != nil { + return nil, err + } + // For reclone and replay, we create a new package every time // the version should be in newObj so we will overwrite. draft, err := repo.CreatePackageRevision(ctx, newObj) @@ -1293,7 +548,7 @@ func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo return nil, err } - if err := cad.applyTasks(ctx, draft, repositoryObj, newObj, packageConfig); err != nil { + if err := cad.taskHandler.ApplyTasks(ctx, draft, repositoryObj, newObj, packageConfig); err != nil { return nil, err } @@ -1301,32 +556,18 @@ func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo return nil, err } - return draft.Close(ctx, version) -} + repoPkgRev, err := draft.Close(ctx, version) -// ExtractContextConfigMap returns the package-context configmap, if found -func ExtractContextConfigMap(resources map[string]string) (*unstructured.Unstructured, error) { - unstructureds, err := objects.Parser{}.AsUnstructureds(resources) if err != nil { return nil, err } - var matches []*unstructured.Unstructured - for _, o := range unstructureds { - configMapGK := schema.GroupKind{Kind: "ConfigMap"} - if o.GroupVersionKind().GroupKind() == configMapGK { - if o.GetName() == builtins.PkgContextName { - matches = append(matches, o) - } - } - } - if len(matches) == 0 { - return nil, nil - } - - if len(matches) > 1 { - return nil, fmt.Errorf("found multiple configmaps matching name %q", builtins.PkgContextFile) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + if err != nil { + return nil, err } - return matches[0], nil + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) + klog.Infof("engine: sent %d for reclone and replay PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) + return repoPkgRev, nil } diff --git a/pkg/engine/options.go b/pkg/engine/options.go index 18f33e93..fc814992 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -34,6 +34,7 @@ type EngineOptionFunc func(engine *cadEngine) error var _ EngineOption = EngineOptionFunc(nil) func (f EngineOptionFunc) apply(engine *cadEngine) error { + engine.taskHandler.RepoOpener = engine return f(engine) } @@ -47,12 +48,12 @@ func WithCache(cache cache.Cache) EngineOption { func WithBuiltinFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { runtime := newBuiltinRuntime() - if engine.runtime == nil { - engine.runtime = runtime - } else if mr, ok := engine.runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.Runtime == nil { + engine.taskHandler.Runtime = runtime + } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.runtime, runtime}) + engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) } return nil }) @@ -64,12 +65,12 @@ func WithGRPCFunctionRuntime(address string) EngineOption { if err != nil { return fmt.Errorf("failed to create function runtime: %w", err) } - if engine.runtime == nil { - engine.runtime = runtime - } else if mr, ok := engine.runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.Runtime == nil { + engine.taskHandler.Runtime = runtime + } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.runtime, runtime}) + engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) } return nil }) @@ -77,14 +78,14 @@ func WithGRPCFunctionRuntime(address string) EngineOption { func WithFunctionRuntime(runtime fn.FunctionRuntime) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runtime = runtime + engine.taskHandler.Runtime = runtime return nil }) } func WithSimpleFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runtime = kpt.NewSimpleFunctionRuntime() + engine.taskHandler.Runtime = kpt.NewSimpleFunctionRuntime() return nil }) } @@ -95,21 +96,21 @@ func WithRunnerOptions(options fnruntime.RunnerOptions) EngineOption { func WithRunnerOptionsResolver(fn func(namespace string) fnruntime.RunnerOptions) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runnerOptionsResolver = fn + engine.taskHandler.RunnerOptionsResolver = fn return nil }) } func WithCredentialResolver(resolver repository.CredentialResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.credentialResolver = resolver + engine.taskHandler.CredentialResolver = resolver return nil }) } -func WithReferenceResolver(resolver ReferenceResolver) EngineOption { +func WithReferenceResolver(resolver repository.ReferenceResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.referenceResolver = resolver + engine.taskHandler.ReferenceResolver = resolver return nil }) } diff --git a/pkg/engine/package.go b/pkg/engine/package.go deleted file mode 100644 index 95394ee9..00000000 --- a/pkg/engine/package.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 The kpt and Nephio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package engine - -import ( - "context" - "fmt" - - api "github.com/nephio-project/porch/api/porch/v1alpha1" - configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/repository" -) - -type PackageFetcher struct { - repoOpener RepositoryOpener - referenceResolver ReferenceResolver -} - -func (p *PackageFetcher) FetchRevision(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (repository.PackageRevision, error) { - repositoryName, err := parseUpstreamRepository(packageRef.Name) - if err != nil { - return nil, err - } - var resolved configapi.Repository - if err := p.referenceResolver.ResolveReference(ctx, namespace, repositoryName, &resolved); err != nil { - return nil, fmt.Errorf("cannot find repository %s/%s: %w", namespace, repositoryName, err) - } - - repo, err := p.repoOpener.OpenRepository(ctx, &resolved) - if err != nil { - return nil, err - } - - revisions, err := repo.ListPackageRevisions(ctx, repository.ListPackageRevisionFilter{KubeObjectName: packageRef.Name}) - if err != nil { - return nil, err - } - - var revision repository.PackageRevision - for _, rev := range revisions { - if rev.KubeObjectName() == packageRef.Name { - revision = rev - break - } - } - if revision == nil { - return nil, fmt.Errorf("cannot find package revision %q", packageRef.Name) - } - - return revision, nil -} - -func (p *PackageFetcher) FetchResources(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (*api.PackageRevisionResources, error) { - revision, err := p.FetchRevision(ctx, packageRef, namespace) - if err != nil { - return nil, err - } - - resources, err := revision.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot read contents of package %q: %w", packageRef.Name, err) - } - return resources, nil -} diff --git a/pkg/engine/testdata/clone/bucket/Kptfile b/pkg/engine/testdata/clone/bucket/Kptfile deleted file mode 100644 index e2b651e4..00000000 --- a/pkg/engine/testdata/clone/bucket/Kptfile +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: bucket -info: - description: Bucket test package diff --git a/pkg/engine/testdata/clone/bucket/bucket.yaml b/pkg/engine/testdata/clone/bucket/bucket.yaml deleted file mode 100644 index 9b75ffcb..00000000 --- a/pkg/engine/testdata/clone/bucket/bucket.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/clone/configmap/Kptfile b/pkg/engine/testdata/clone/configmap/Kptfile deleted file mode 100644 index 54fdea56..00000000 --- a/pkg/engine/testdata/clone/configmap/Kptfile +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: configmap -info: - description: ConfigMap test package diff --git a/pkg/engine/testdata/clone/configmap/configmap.yaml b/pkg/engine/testdata/clone/configmap/configmap.yaml deleted file mode 100644 index 8b4c9896..00000000 --- a/pkg/engine/testdata/clone/configmap/configmap.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: configmap-name - namespace: configmap-namespace -data: - key: value diff --git a/pkg/engine/testdata/context/expected/Kptfile b/pkg/engine/testdata/context/expected/Kptfile deleted file mode 100644 index 1e92a6cf..00000000 --- a/pkg/engine/testdata/context/expected/Kptfile +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: input -info: - description: Builtin Function Test Package diff --git a/pkg/engine/testdata/context/expected/bucket.yaml b/pkg/engine/testdata/context/expected/bucket.yaml deleted file mode 100644 index 5dad26d4..00000000 --- a/pkg/engine/testdata/context/expected/bucket.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/context/expected/package-context.yaml b/pkg/engine/testdata/context/expected/package-context.yaml deleted file mode 100644 index 57b4e523..00000000 --- a/pkg/engine/testdata/context/expected/package-context.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: input - package-path: parent1/parent1.2/parent1.2.3/me diff --git a/pkg/engine/testdata/context/input/Kptfile b/pkg/engine/testdata/context/input/Kptfile deleted file mode 100644 index 1e92a6cf..00000000 --- a/pkg/engine/testdata/context/input/Kptfile +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: input -info: - description: Builtin Function Test Package diff --git a/pkg/engine/testdata/context/input/bucket.yaml b/pkg/engine/testdata/context/input/bucket.yaml deleted file mode 100644 index 5dad26d4..00000000 --- a/pkg/engine/testdata/context/input/bucket.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/init/testpkg/Kptfile b/pkg/engine/testdata/init/testpkg/Kptfile deleted file mode 100644 index 7883a649..00000000 --- a/pkg/engine/testdata/init/testpkg/Kptfile +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: testpkg - annotations: - config.kubernetes.io/local-config: "true" -info: - site: http://kpt.dev/testpkg - description: test package - keywords: - - test - - kpt - - pkg diff --git a/pkg/engine/testdata/init/testpkg/README.md b/pkg/engine/testdata/init/testpkg/README.md deleted file mode 100644 index 9a34a509..00000000 --- a/pkg/engine/testdata/init/testpkg/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# testpkg - -## Description -test package - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] testpkg` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree testpkg` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init testpkg -kpt live apply testpkg --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/init/testpkg/package-context.yaml b/pkg/engine/testdata/init/testpkg/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/init/testpkg/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/replace/Kptfile b/pkg/engine/testdata/replace/Kptfile deleted file mode 100644 index 9529df9a..00000000 --- a/pkg/engine/testdata/replace/Kptfile +++ /dev/null @@ -1,23 +0,0 @@ -# top comment -apiVersion: kpt.dev/v1 -# Kptfile info -info: - # Kptfile description - description: A Google Cloud Storage bucket -# Kptfile kind -kind: Kptfile -# Kptfile metadata -metadata: - annotations: - blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint - name: simple-bucket -# Kptfile pipeline -pipeline: - # Kptfile mutators - mutators: - - configMap: - name: updated-bucket-name - namespace: updated-namespace - project-id: updated-project-id - storage-class: updated-storage-class - image: gcr.io/kpt-fn/apply-setters:v0.2.0 diff --git a/pkg/engine/testdata/replace/README.md b/pkg/engine/testdata/replace/README.md deleted file mode 100644 index 6f0623fa..00000000 --- a/pkg/engine/testdata/replace/README.md +++ /dev/null @@ -1 +0,0 @@ -# replace test \ No newline at end of file diff --git a/pkg/engine/testdata/replace/bucket.yaml b/pkg/engine/testdata/replace/bucket.yaml deleted file mode 100644 index 935e6573..00000000 --- a/pkg/engine/testdata/replace/bucket.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# top comment -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -# metadata comment -metadata: # kpt-merge: config-control/blueprints-project-bucket - # annotations comment - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} - name: blueprints-project-bucket # kpt-set: ${project-id}-${name} - namespace: config-control # kpt-set: ${namespace} -# spec comment -spec: - storageClass: standard # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - # Versioning is enabled - versioning: - enabled: false diff --git a/pkg/engine/testdata/simple-render/expected.txt b/pkg/engine/testdata/simple-render/expected.txt deleted file mode 100644 index f625b40c..00000000 --- a/pkg/engine/testdata/simple-render/expected.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: # kpt-merge: config-control/blueprints-project-bucket - name: updated-project-id-updated-bucket-name # kpt-set: ${project-id}-${name} - namespace: updated-namespace # kpt-set: ${namespace} - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: updated-project-id # kpt-set: ${project-id} - cnrm.cloud.google.com/blueprint: 'kpt-fn' -spec: - storageClass: updated-storage-class # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/simple-render/simple-bucket/Kptfile b/pkg/engine/testdata/simple-render/simple-bucket/Kptfile deleted file mode 100644 index 7a6d5063..00000000 --- a/pkg/engine/testdata/simple-render/simple-bucket/Kptfile +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: simple-bucket - annotations: - blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint -info: - description: A Google Cloud Storage bucket -pipeline: - mutators: - - image: gcr.io/kpt-fn/apply-setters:v0.2.0 - configMap: - name: updated-bucket-name - namespace: updated-namespace - project-id: updated-project-id - storage-class: updated-storage-class diff --git a/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml b/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml deleted file mode 100644 index f80f88b6..00000000 --- a/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: # kpt-merge: config-control/blueprints-project-bucket - name: blueprints-project-bucket # kpt-set: ${project-id}-${name} - namespace: config-control # kpt-set: ${namespace} - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} -spec: - storageClass: standard # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/update/local/Kptfile b/pkg/engine/testdata/update/local/Kptfile deleted file mode 100644 index 56adbced..00000000 --- a/pkg/engine/testdata/update/local/Kptfile +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: backend - annotations: - config.kubernetes.io/local-config: "true" -upstream: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 -upstreamLock: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 - commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/local/README.md b/pkg/engine/testdata/update/local/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/local/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/local/namespace.yaml b/pkg/engine/testdata/update/local/namespace.yaml deleted file mode 100644 index 5a044679..00000000 --- a/pkg/engine/testdata/update/local/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: # kpt-merge: example - name: backend -spec: {} diff --git a/pkg/engine/testdata/update/local/package-context.yaml b/pkg/engine/testdata/update/local/package-context.yaml deleted file mode 100644 index 9b77f45e..00000000 --- a/pkg/engine/testdata/update/local/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: backend diff --git a/pkg/engine/testdata/update/local/resourcequota.yaml b/pkg/engine/testdata/update/local/resourcequota.yaml deleted file mode 100644 index 02e22b08..00000000 --- a/pkg/engine/testdata/update/local/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: # kpt-merge: example/default - name: default - namespace: backend -spec: - hard: - cpu: "40" - memory: 40G diff --git a/pkg/engine/testdata/update/local/rolebinding.yaml b/pkg/engine/testdata/update/local/rolebinding.yaml deleted file mode 100644 index f57f1f2e..00000000 --- a/pkg/engine/testdata/update/local/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: # kpt-merge: example/app-admin - name: app-admin - namespace: backend -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: backend.admin@bigco.com diff --git a/pkg/engine/testdata/update/local/update-rolebinding.yaml b/pkg/engine/testdata/update/local/update-rolebinding.yaml deleted file mode 100644 index 6fdff582..00000000 --- a/pkg/engine/testdata/update/local/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: # kpt-merge: example/update-rolebinding - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/original/Kptfile b/pkg/engine/testdata/update/original/Kptfile deleted file mode 100644 index db627135..00000000 --- a/pkg/engine/testdata/update/original/Kptfile +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: basens - annotations: - config.kubernetes.io/local-config: "true" -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/original/README.md b/pkg/engine/testdata/update/original/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/original/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/original/namespace.yaml b/pkg/engine/testdata/update/original/namespace.yaml deleted file mode 100644 index 9219ae32..00000000 --- a/pkg/engine/testdata/update/original/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: example -spec: {} diff --git a/pkg/engine/testdata/update/original/package-context.yaml b/pkg/engine/testdata/update/original/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/update/original/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/update/original/resourcequota.yaml b/pkg/engine/testdata/update/original/resourcequota.yaml deleted file mode 100644 index a5c7f9dc..00000000 --- a/pkg/engine/testdata/update/original/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: - name: default - namespace: example -spec: - hard: - cpu: "40" - memory: 40G diff --git a/pkg/engine/testdata/update/original/rolebinding.yaml b/pkg/engine/testdata/update/original/rolebinding.yaml deleted file mode 100644 index 12ce8875..00000000 --- a/pkg/engine/testdata/update/original/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: app-admin - namespace: example -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: example.admin@bigco.com diff --git a/pkg/engine/testdata/update/original/update-rolebinding.yaml b/pkg/engine/testdata/update/original/update-rolebinding.yaml deleted file mode 100644 index a20edf50..00000000 --- a/pkg/engine/testdata/update/original/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/updated/Kptfile b/pkg/engine/testdata/update/updated/Kptfile deleted file mode 100644 index 56adbced..00000000 --- a/pkg/engine/testdata/update/updated/Kptfile +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: backend - annotations: - config.kubernetes.io/local-config: "true" -upstream: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 -upstreamLock: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 - commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/updated/README.md b/pkg/engine/testdata/update/updated/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/updated/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/updated/namespace.yaml b/pkg/engine/testdata/update/updated/namespace.yaml deleted file mode 100644 index 5a044679..00000000 --- a/pkg/engine/testdata/update/updated/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: # kpt-merge: example - name: backend -spec: {} diff --git a/pkg/engine/testdata/update/updated/package-context.yaml b/pkg/engine/testdata/update/updated/package-context.yaml deleted file mode 100644 index 9b77f45e..00000000 --- a/pkg/engine/testdata/update/updated/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: backend diff --git a/pkg/engine/testdata/update/updated/resourcequota.yaml b/pkg/engine/testdata/update/updated/resourcequota.yaml deleted file mode 100644 index e7bc953e..00000000 --- a/pkg/engine/testdata/update/updated/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: # kpt-merge: example/default - name: default - namespace: backend -spec: - hard: - cpu: "40" - memory: 60G diff --git a/pkg/engine/testdata/update/updated/rolebinding.yaml b/pkg/engine/testdata/update/updated/rolebinding.yaml deleted file mode 100644 index f57f1f2e..00000000 --- a/pkg/engine/testdata/update/updated/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: # kpt-merge: example/app-admin - name: app-admin - namespace: backend -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: backend.admin@bigco.com diff --git a/pkg/engine/testdata/update/updated/update-rolebinding.yaml b/pkg/engine/testdata/update/updated/update-rolebinding.yaml deleted file mode 100644 index 6fdff582..00000000 --- a/pkg/engine/testdata/update/updated/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: # kpt-merge: example/update-rolebinding - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/upstream/Kptfile b/pkg/engine/testdata/update/upstream/Kptfile deleted file mode 100644 index 65fe6a65..00000000 --- a/pkg/engine/testdata/update/upstream/Kptfile +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: basens - annotations: - config.kubernetes.io/local-config: "true" -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/upstream/README.md b/pkg/engine/testdata/update/upstream/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/upstream/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/upstream/namespace.yaml b/pkg/engine/testdata/update/upstream/namespace.yaml deleted file mode 100644 index 9219ae32..00000000 --- a/pkg/engine/testdata/update/upstream/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: example -spec: {} diff --git a/pkg/engine/testdata/update/upstream/package-context.yaml b/pkg/engine/testdata/update/upstream/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/update/upstream/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/update/upstream/resourcequota.yaml b/pkg/engine/testdata/update/upstream/resourcequota.yaml deleted file mode 100644 index 5a56078d..00000000 --- a/pkg/engine/testdata/update/upstream/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: - name: default - namespace: example -spec: - hard: - cpu: "40" - memory: 60G diff --git a/pkg/engine/testdata/update/upstream/rolebinding.yaml b/pkg/engine/testdata/update/upstream/rolebinding.yaml deleted file mode 100644 index 12ce8875..00000000 --- a/pkg/engine/testdata/update/upstream/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: app-admin - namespace: example -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: example.admin@bigco.com diff --git a/pkg/engine/testdata/update/upstream/update-rolebinding.yaml b/pkg/engine/testdata/update/upstream/update-rolebinding.yaml deleted file mode 100644 index a20edf50..00000000 --- a/pkg/engine/testdata/update/upstream/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/watchermanager.go b/pkg/engine/watchermanager.go index 5a2d74eb..b64c30be 100644 --- a/pkg/engine/watchermanager.go +++ b/pkg/engine/watchermanager.go @@ -18,7 +18,6 @@ import ( "context" "sync" - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" @@ -31,7 +30,7 @@ type WatcherManager interface { // PackageRevisionWatcher is the callback interface for watchers. type ObjectWatcher interface { - OnPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) bool + OnPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) bool } func NewWatcherManager() *watcherManager { @@ -95,7 +94,7 @@ func (r *watcherManager) WatchPackageRevisions(ctx context.Context, filter repos } // notifyPackageRevisionChange is called to send a change notification to all interested listeners. -func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int { +func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) int { r.mutex.Lock() defer r.mutex.Unlock() @@ -109,7 +108,7 @@ func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, r.watchers[i] = nil continue } - if keepGoing := watcher.callback.OnPackageRevisionChange(eventType, obj, objMeta); !keepGoing { + if keepGoing := watcher.callback.OnPackageRevisionChange(eventType, obj); !keepGoing { klog.Infof("stopping watcher in response to !keepGoing") r.watchers[i] = nil } diff --git a/pkg/git/git.go b/pkg/git/git.go index 4794acba..bca58d2a 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1107,7 +1107,7 @@ func (r *gitRepository) pushAndCleanup(ctx context.Context, ph *pushRefSpecBuild return nil } -func (r *gitRepository) loadTasks(ctx context.Context, startCommit *object.Commit, packagePath string, +func (r *gitRepository) loadTasks(_ context.Context, startCommit *object.Commit, packagePath string, workspaceName v1alpha1.WorkspaceName) ([]v1alpha1.Task, error) { var logOptions = git.LogOptions{ @@ -1709,6 +1709,10 @@ func (r *gitRepository) discoverPackagesInTree(commit *object.Commit, opt Discov return t, nil } +func (r *gitRepository) Refresh(_ context.Context) error { + return nil +} + // See https://eli.thegreenplace.net/2021/generic-functions-on-slices-with-go-type-parameters/ // func ReverseSlice[T any](s []T) { // Ready for generics! func reverseSlice(s []v1alpha1.Task) { diff --git a/pkg/git/package.go b/pkg/git/package.go index c39962d6..b3c2ff26 100644 --- a/pkg/git/package.go +++ b/pkg/git/package.go @@ -29,6 +29,7 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/internal/kpt/pkg" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -50,6 +51,7 @@ type gitPackageRevision struct { tree plumbing.Hash // Cached tree of the package itself, some descendent of commit.Tree() commit plumbing.Hash // Current version of the package (commit sha) tasks []v1alpha1.Task + metadata meta.PackageRevisionMeta } var _ repository.PackageRevision = &gitPackageRevision{} @@ -316,4 +318,12 @@ func (p *gitPackageRevision) UpdateLifecycle(ctx context.Context, new v1alpha1.P return p.repo.UpdateLifecycle(ctx, p, new) } +func (p *gitPackageRevision) GetMeta() meta.PackageRevisionMeta { + return p.metadata +} + +func (p *gitPackageRevision) SetMeta(metadata meta.PackageRevisionMeta) { + p.metadata = metadata +} + // TODO: Define a type `gitPackage` to implement the Repository.Package interface diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index e43157fe..4b683ac8 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -31,6 +31,7 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/internal/kpt/pkg" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -244,6 +245,10 @@ func (r *ociRepository) buildPackageRevision(ctx context.Context, name oci.Image return p, nil } +func (r *ociRepository) Refresh(_ context.Context) error { + return nil +} + // ToMainPackageRevision implements repository.PackageRevision. func (p *ociPackageRevision) ToMainPackageRevision() repository.PackageRevision { panic("unimplemented") @@ -257,6 +262,7 @@ type ociPackageRevision struct { created time.Time resourceVersion string uid types.UID + metadata meta.PackageRevisionMeta parent *ociRepository tasks []v1alpha1.Task @@ -418,3 +424,11 @@ func (p *ociPackageRevision) UpdateLifecycle(ctx context.Context, new v1alpha1.P } return nil } + +func (p *ociPackageRevision) GetMeta() meta.PackageRevisionMeta { + return p.metadata +} + +func (p *ociPackageRevision) SetMeta(metadata meta.PackageRevisionMeta) { + p.metadata = metadata +} diff --git a/pkg/registry/porch/package.go b/pkg/registry/porch/package.go index 87978404..e5d8414f 100644 --- a/pkg/registry/porch/package.go +++ b/pkg/registry/porch/package.go @@ -19,7 +19,7 @@ import ( "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -46,7 +46,7 @@ var _ rest.GracefulDeleter = &packages{} var _ rest.SingularNameProvider = &packages{} // GetSingularName implements the SingularNameProvider interface -func (r *packages) GetSingularName() (string) { +func (r *packages) GetSingularName() string { return "package" } @@ -81,7 +81,7 @@ func (r *packages) List(ctx context.Context, options *metainternalversion.ListOp return nil, err } - if err := r.packageCommon.listPackages(ctx, filter, func(p *engine.Package) error { + if err := r.packageCommon.listPackages(ctx, filter, func(p repository.Package) error { item := p.GetPackage() result.Items = append(result.Items, *item) return nil @@ -157,7 +157,7 @@ func (r *packages) Create(ctx context.Context, runtimeObject runtime.Object, cre // Update finds a resource in the storage and updates it. Some implementations // may allow updates creates the object - they should set the created boolean // to true. -func (r *packages) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, +func (r *packages) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { ctx, span := tracer.Start(ctx, "packages::Update", trace.WithAttributes()) defer span.End() diff --git a/pkg/registry/porch/packagecommon.go b/pkg/registry/porch/packagecommon.go index cb2f66af..74ddb44b 100644 --- a/pkg/registry/porch/packagecommon.go +++ b/pkg/registry/porch/packagecommon.go @@ -24,6 +24,7 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/pkg/engine" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/util" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -55,7 +56,7 @@ type packageCommon struct { } func (r *packageCommon) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, - selector labels.Selector, callback func(p *engine.PackageRevision) error) error { + selector labels.Selector, callback func(p repository.PackageRevision) error) error { var opts []client.ListOption ns, namespaced := genericapirequest.NamespaceFrom(ctx) if namespaced && ns != "" { @@ -102,7 +103,7 @@ func (r *packageCommon) listPackageRevisions(ctx context.Context, filter package return nil } -func (r *packageCommon) listPackages(ctx context.Context, filter packageFilter, callback func(p *engine.Package) error) error { +func (r *packageCommon) listPackages(ctx context.Context, filter packageFilter, callback func(p repository.Package) error) error { var opts []client.ListOption if ns, namespaced := genericapirequest.NamespaceFrom(ctx); namespaced { opts = append(opts, client.InNamespace(ns)) @@ -152,7 +153,7 @@ func (r *packageCommon) getRepositoryObjFromName(ctx context.Context, name strin if !namespaced { return nil, fmt.Errorf("namespace must be specified") } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, apierrors.NewNotFound(r.gr, name) } @@ -171,7 +172,7 @@ func (r *packageCommon) getRepositoryObj(ctx context.Context, repositoryID types return &repositoryObj, nil } -func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (*engine.PackageRevision, error) { +func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (repository.PackageRevision, error) { repositoryObj, err := r.getRepositoryObjFromName(ctx, name) if err != nil { return nil, err @@ -189,7 +190,7 @@ func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (*engine return nil, apierrors.NewNotFound(r.gr, name) } -func (r *packageCommon) getPackage(ctx context.Context, name string) (*engine.Package, error) { +func (r *packageCommon) getPackage(ctx context.Context, name string) (repository.Package, error) { repositoryObj, err := r.getRepositoryObjFromName(ctx, name) if err != nil { return nil, err @@ -279,7 +280,7 @@ func (r *packageCommon) updatePackageRevision(ctx context.Context, name string, return nil, false, apierrors.NewBadRequest(fmt.Sprintf("expected PackageRevision object, got %T", newRuntimeObj)) } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } @@ -299,7 +300,7 @@ func (r *packageCommon) updatePackageRevision(ctx context.Context, name string, return nil, false, apierrors.NewInternalError(fmt.Errorf("error getting repository %v: %w", repositoryID, err)) } - var parentPackage *engine.PackageRevision + var parentPackage repository.PackageRevision if newApiPkgRev.Spec.Parent != nil && newApiPkgRev.Spec.Parent.Name != "" { p, err := r.getRepoPkgRev(ctx, newApiPkgRev.Spec.Parent.Name) if err != nil { @@ -391,7 +392,7 @@ func (r *packageCommon) updatePackage(ctx context.Context, name string, objInfo return nil, false, apierrors.NewBadRequest(fmt.Sprintf("expected Package object, got %T", newRuntimeObj)) } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } @@ -441,7 +442,7 @@ func (r *packageCommon) validateDelete(ctx context.Context, deleteValidation res return nil, err } } - repositoryName, err := ParseRepositoryName(repoName) + repositoryName, err := util.ParseRepositoryName(repoName) if err != nil { return nil, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", repoName)) } diff --git a/pkg/registry/porch/packagerevision.go b/pkg/registry/porch/packagerevision.go index dc4e7cbd..357d1ae7 100644 --- a/pkg/registry/porch/packagerevision.go +++ b/pkg/registry/porch/packagerevision.go @@ -19,7 +19,7 @@ import ( "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -85,7 +85,7 @@ func (r *packageRevisions) List(ctx context.Context, options *metainternalversio return nil, err } - if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p *engine.PackageRevision) error { + if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p repository.PackageRevision) error { item, err := p.GetPackageRevision(ctx) if err != nil { return err @@ -155,7 +155,7 @@ func (r *packageRevisions) Create(ctx context.Context, runtimeObject runtime.Obj return nil, apierrors.NewInvalid(api.SchemeGroupVersion.WithKind("PackageRevision").GroupKind(), newApiPkgRev.Name, fieldErrors) } - var parentPackage *engine.PackageRevision + var parentPackage repository.PackageRevision if newApiPkgRev.Spec.Parent != nil && newApiPkgRev.Spec.Parent.Name != "" { p, err := r.packageCommon.getRepoPkgRev(ctx, newApiPkgRev.Spec.Parent.Name) if err != nil { diff --git a/pkg/registry/porch/packagerevisionresources.go b/pkg/registry/porch/packagerevisionresources.go index 4f71a443..bd6fc6fc 100644 --- a/pkg/registry/porch/packagerevisionresources.go +++ b/pkg/registry/porch/packagerevisionresources.go @@ -22,7 +22,8 @@ import ( api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/util" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -82,7 +83,7 @@ func (r *packageRevisionResources) List(ctx context.Context, options *metaintern return nil, err } - if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p *engine.PackageRevision) error { + if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p repository.PackageRevision) error { apiPkgResources, err := p.GetResources(ctx) if err != nil { return err @@ -166,7 +167,7 @@ func (r *packageRevisionResources) Update(ctx context.Context, name string, objI } } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } diff --git a/pkg/registry/porch/reference.go b/pkg/registry/porch/reference.go index 8c4b64ee..5fce1ab9 100644 --- a/pkg/registry/porch/reference.go +++ b/pkg/registry/porch/reference.go @@ -17,11 +17,11 @@ package porch import ( "context" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewReferenceResolver(coreClient client.Reader) engine.ReferenceResolver { +func NewReferenceResolver(coreClient client.Reader) repository.ReferenceResolver { return &referenceResolver{ coreClient: coreClient, } @@ -31,9 +31,9 @@ type referenceResolver struct { coreClient client.Reader } -var _ engine.ReferenceResolver = &referenceResolver{} +var _ repository.ReferenceResolver = &referenceResolver{} -func (r *referenceResolver) ResolveReference(ctx context.Context, namespace, name string, result engine.Object) error { +func (r *referenceResolver) ResolveReference(ctx context.Context, namespace, name string, result repository.Object) error { return r.coreClient.Get(ctx, client.ObjectKey{ Namespace: namespace, Name: name, diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index f3927885..3b721046 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -20,7 +20,6 @@ import ( "sync" "github.com/nephio-project/porch/pkg/engine" - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -72,7 +71,7 @@ type watcher struct { // mutex that protects the eventCallback and done fields // from concurrent access. mutex sync.Mutex - eventCallback func(eventType watch.EventType, pr engine.PackageRevision) bool + eventCallback func(eventType watch.EventType, pr repository.PackageRevision) bool done bool totalSent int } @@ -94,7 +93,7 @@ func (w *watcher) ResultChan() <-chan watch.Event { type packageReader interface { watchPackages(ctx context.Context, filter packageRevisionFilter, callback engine.ObjectWatcher) error - listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p *engine.PackageRevision) error) error + listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p repository.PackageRevision) error) error } // listAndWatch implements watch by doing a list, then sending any observed changes. @@ -120,7 +119,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter // Make sure we hold the lock when setting the eventCallback, as it // will be read by other goroutines when events happen. w.mutex.Lock() - w.eventCallback = func(eventType watch.EventType, pr engine.PackageRevision) bool { + w.eventCallback = func(eventType watch.EventType, pr repository.PackageRevision) bool { if w.done { return false } @@ -146,7 +145,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter sentAdd := 0 // TODO: Only if rv == 0? - if err := r.listPackageRevisions(ctx, filter, selector, func(p *engine.PackageRevision) error { + if err := r.listPackageRevisions(ctx, filter, selector, func(p repository.PackageRevision) error { obj, err := p.GetPackageRevision(ctx) if err != nil { w.mutex.Lock() @@ -199,7 +198,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter } klog.Infof("watch %p: moving watch into streaming mode after sentAdd %d, sentBacklog %d, sentNewBacklog %d", w, sentAdd, sentBacklog, sentNewBacklog) - w.eventCallback = func(eventType watch.EventType, pr engine.PackageRevision) bool { + w.eventCallback = func(eventType watch.EventType, pr repository.PackageRevision) bool { if w.done { return false } @@ -245,9 +244,9 @@ func (w *watcher) sendWatchEvent(ev watch.Event) { } // OnPackageRevisionChange is the callback called when a PackageRevision changes. -func (w *watcher) OnPackageRevisionChange(eventType watch.EventType, pr repository.PackageRevision, objMeta meta.PackageRevisionMeta) bool { +func (w *watcher) OnPackageRevisionChange(eventType watch.EventType, pr repository.PackageRevision) bool { w.mutex.Lock() defer w.mutex.Unlock() - return w.eventCallback(eventType, *engine.ToPackageRevision(pr, objMeta)) + return w.eventCallback(eventType, pr) } diff --git a/pkg/registry/porch/watch_test.go b/pkg/registry/porch/watch_test.go index 3a32a788..bbf91d91 100644 --- a/pkg/registry/porch/watch_test.go +++ b/pkg/registry/porch/watch_test.go @@ -22,8 +22,8 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/pkg/engine" - "github.com/nephio-project/porch/pkg/engine/fake" - "github.com/nephio-project/porch/pkg/meta" + "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -64,14 +64,14 @@ func TestWatcherClose(t *testing.T) { for { select { case <-ch: - pkgRev := &fake.PackageRevision{ + pkgRev := &fake.FakePackageRevision{ PackageRevision: &v1alpha1.PackageRevision{ ObjectMeta: metav1.ObjectMeta{ Labels: make(map[string]string), }, }, } - if cont := r.callback.OnPackageRevisionChange(watch.Modified, pkgRev, meta.PackageRevisionMeta{}); !cont { + if cont := r.callback.OnPackageRevisionChange(watch.Modified, pkgRev); !cont { return } case <-timer.C: @@ -97,6 +97,6 @@ func (f *fakePackageReader) watchPackages(ctx context.Context, filter packageRev return nil } -func (f *fakePackageReader) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p *engine.PackageRevision) error) error { +func (f *fakePackageReader) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p repository.PackageRevision) error) error { return nil } diff --git a/pkg/engine/environment.go b/pkg/repository/environment.go similarity index 80% rename from pkg/engine/environment.go rename to pkg/repository/environment.go index 7cb99378..96d542dc 100644 --- a/pkg/engine/environment.go +++ b/pkg/repository/environment.go @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -26,6 +27,10 @@ type Object interface { runtime.Object } +type RepositoryOpener interface { + OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (Repository, error) +} + type ReferenceResolver interface { ResolveReference(ctx context.Context, namespace, name string, result Object) error } diff --git a/pkg/engine/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go similarity index 53% rename from pkg/engine/fake/packagerevision.go rename to pkg/repository/fake/packagerevision.go index 713c0b99..6fb1fcba 100644 --- a/pkg/engine/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -19,12 +19,13 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/types" ) // Implementation of the repository.PackageRevision interface for testing. -type PackageRevision struct { +type FakePackageRevision struct { Name string Namespace string Uid types.UID @@ -35,57 +36,71 @@ type PackageRevision struct { Kptfile kptfile.KptFile } -func (pr *PackageRevision) KubeObjectName() string { +func (pr *FakePackageRevision) KubeObjectName() string { return pr.Name } -var _ repository.PackageRevision = &PackageRevision{} +var _ repository.PackageRevision = &FakePackageRevision{} // ToMainPackageRevision implements repository.PackageRevision. -func (f *PackageRevision) ToMainPackageRevision() repository.PackageRevision { +func (f *FakePackageRevision) ToMainPackageRevision() repository.PackageRevision { panic("unimplemented") } -func (pr *PackageRevision) KubeObjectNamespace() string { +func (pr *FakePackageRevision) KubeObjectNamespace() string { return pr.Namespace } -func (pr *PackageRevision) UID() types.UID { +func (pr *FakePackageRevision) UID() types.UID { return pr.Uid } -func (pr *PackageRevision) ResourceVersion() string { +func (pr *FakePackageRevision) ResourceVersion() string { return pr.PackageRevision.ResourceVersion } -func (pr *PackageRevision) Key() repository.PackageRevisionKey { +func (pr *FakePackageRevision) Key() repository.PackageRevisionKey { return pr.PackageRevisionKey } -func (pr *PackageRevision) Lifecycle() v1alpha1.PackageRevisionLifecycle { +func (pr *FakePackageRevision) Lifecycle() v1alpha1.PackageRevisionLifecycle { return pr.PackageLifecycle } -func (pr *PackageRevision) GetPackageRevision(context.Context) (*v1alpha1.PackageRevision, error) { +func (pr *FakePackageRevision) GetPackageRevision(context.Context) (*v1alpha1.PackageRevision, error) { return pr.PackageRevision, nil } -func (f *PackageRevision) GetResources(context.Context) (*v1alpha1.PackageRevisionResources, error) { +func (f *FakePackageRevision) GetResources(context.Context) (*v1alpha1.PackageRevisionResources, error) { return f.Resources, nil } -func (f *PackageRevision) GetKptfile(ctx context.Context) (kptfile.KptFile, error) { +func (f *FakePackageRevision) GetKptfile(ctx context.Context) (kptfile.KptFile, error) { return f.Kptfile, nil } -func (f *PackageRevision) GetUpstreamLock(context.Context) (kptfile.Upstream, kptfile.UpstreamLock, error) { +func (f *FakePackageRevision) GetUpstreamLock(context.Context) (kptfile.Upstream, kptfile.UpstreamLock, error) { return *f.Kptfile.Upstream, *f.Kptfile.UpstreamLock, nil } -func (f *PackageRevision) GetLock() (kptfile.Upstream, kptfile.UpstreamLock, error) { +func (f *FakePackageRevision) GetLock() (kptfile.Upstream, kptfile.UpstreamLock, error) { return *f.Kptfile.Upstream, *f.Kptfile.UpstreamLock, nil } -func (f *PackageRevision) UpdateLifecycle(context.Context, v1alpha1.PackageRevisionLifecycle) error { +func (f *FakePackageRevision) UpdateLifecycle(context.Context, v1alpha1.PackageRevisionLifecycle) error { return nil } + +func (f *FakePackageRevision) GetMeta() meta.PackageRevisionMeta { + return meta.PackageRevisionMeta{} +} + +func (f *FakePackageRevision) SetMeta(meta.PackageRevisionMeta) { +} + +func (f *FakePackageRevision) IsLatest() bool { + return false +} + +func (f *FakePackageRevision) SetLatest(latest bool) { +} diff --git a/pkg/engine/fake/repository.go b/pkg/repository/fake/repository.go similarity index 97% rename from pkg/engine/fake/repository.go rename to pkg/repository/fake/repository.go index be01a388..ff9abd72 100644 --- a/pkg/engine/fake/repository.go +++ b/pkg/repository/fake/repository.go @@ -80,3 +80,7 @@ func (r *Repository) CreatePackage(_ context.Context, pr *v1alpha1.PorchPackage) func (r *Repository) DeletePackage(_ context.Context, pr repository.Package) error { return nil } + +func (r *Repository) Refresh(_ context.Context) error { + return nil +} diff --git a/pkg/repository/package.go b/pkg/repository/package.go new file mode 100644 index 00000000..77836afd --- /dev/null +++ b/pkg/repository/package.go @@ -0,0 +1,153 @@ +// Copyright 2022 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package repository + +import ( + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/pkg/objects" + "github.com/nephio-project/porch/pkg/util" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type PackageFetcher struct { + RepoOpener RepositoryOpener + ReferenceResolver ReferenceResolver +} + +func (p *PackageFetcher) FetchRevision(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (PackageRevision, error) { + repositoryName, err := util.ParseRepositoryName(packageRef.Name) + if err != nil { + return nil, err + } + var resolved configapi.Repository + if err := p.ReferenceResolver.ResolveReference(ctx, namespace, repositoryName, &resolved); err != nil { + return nil, fmt.Errorf("cannot find repository %s/%s: %w", namespace, repositoryName, err) + } + + repo, err := p.RepoOpener.OpenRepository(ctx, &resolved) + if err != nil { + return nil, err + } + + revisions, err := repo.ListPackageRevisions(ctx, ListPackageRevisionFilter{KubeObjectName: packageRef.Name}) + if err != nil { + return nil, err + } + + var revision PackageRevision + for _, rev := range revisions { + if rev.KubeObjectName() == packageRef.Name { + revision = rev + break + } + } + if revision == nil { + return nil, fmt.Errorf("cannot find package revision %q", packageRef.Name) + } + + return revision, nil +} + +func (p *PackageFetcher) FetchResources(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (*api.PackageRevisionResources, error) { + revision, err := p.FetchRevision(ctx, packageRef, namespace) + if err != nil { + return nil, err + } + + resources, err := revision.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot read contents of package %q: %w", packageRef.Name, err) + } + return resources, nil +} + +func BuildPackageConfig(ctx context.Context, obj *api.PackageRevision, parent PackageRevision) (*builtins.PackageConfig, error) { + config := &builtins.PackageConfig{} + + parentPath := "" + + var parentConfig *unstructured.Unstructured + if parent != nil { + parentObj, err := parent.GetPackageRevision(ctx) + if err != nil { + return nil, err + } + parentPath = parentObj.Spec.PackageName + + resources, err := parent.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("error getting resources from parent package %q: %w", parentObj.Name, err) + } + configMapObj, err := extractContextConfigMap(resources.Spec.Resources) + if err != nil { + return nil, fmt.Errorf("error getting configuration from parent package %q: %w", parentObj.Name, err) + } + parentConfig = configMapObj + + if parentConfig != nil { + // TODO: Should we support kinds other than configmaps? + var parentConfigMap corev1.ConfigMap + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(parentConfig.Object, &parentConfigMap); err != nil { + return nil, fmt.Errorf("error parsing ConfigMap from parent configuration: %w", err) + } + if s := parentConfigMap.Data[builtins.ConfigKeyPackagePath]; s != "" { + parentPath = s + "/" + parentPath + } + } + } + + if parentPath == "" { + config.PackagePath = obj.Spec.PackageName + } else { + config.PackagePath = parentPath + "/" + obj.Spec.PackageName + } + + return config, nil +} + +// ExtractContextConfigMap returns the package-context configmap, if found +func extractContextConfigMap(resources map[string]string) (*unstructured.Unstructured, error) { + unstructureds, err := objects.Parser{}.AsUnstructureds(resources) + if err != nil { + return nil, err + } + + var matches []*unstructured.Unstructured + for _, o := range unstructureds { + configMapGK := schema.GroupKind{Kind: "ConfigMap"} + if o.GroupVersionKind().GroupKind() == configMapGK { + if o.GetName() == builtins.PkgContextName { + matches = append(matches, o) + } + } + } + if len(matches) == 0 { + return nil, nil + } + + if len(matches) > 1 { + return nil, fmt.Errorf("found multiple configmaps matching name %q", builtins.PkgContextFile) + } + + return matches[0], nil +} diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 96e21c8e..91f620de 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -21,6 +21,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/nephio-project/porch/api/porch/v1alpha1" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "k8s.io/apimachinery/pkg/types" ) @@ -95,6 +96,9 @@ type PackageRevision interface { ResourceVersion() string ToMainPackageRevision() PackageRevision + + GetMeta() meta.PackageRevisionMeta + SetMeta(meta.PackageRevisionMeta) } // Package is an abstract package. @@ -214,6 +218,9 @@ type Repository interface { // Close cleans up any resources associated with the repository Close() error + + // Refresh the repository + Refresh(ctx context.Context) error } // The definitions below would be more appropriately located in a package usable by any Porch component. diff --git a/pkg/engine/testing.go b/pkg/repository/testing.go similarity index 86% rename from pkg/engine/testing.go rename to pkg/repository/testing.go index bcdd3078..6a37f03f 100644 --- a/pkg/engine/testing.go +++ b/pkg/repository/testing.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "fmt" @@ -20,11 +20,9 @@ import ( "os" "path/filepath" "testing" - - "github.com/nephio-project/porch/pkg/repository" ) -func readPackage(t *testing.T, packageDir string) repository.PackageResources { +func ReadPackage(t *testing.T, packageDir string) PackageResources { results := map[string]string{} if err := filepath.Walk(packageDir, func(p string, info fs.FileInfo, err error) error { @@ -49,12 +47,12 @@ func readPackage(t *testing.T, packageDir string) repository.PackageResources { }); err != nil { t.Errorf("Failed to read package from disk %q: %v", packageDir, err) } - return repository.PackageResources{ + return PackageResources{ Contents: results, } } -func writePackage(t *testing.T, packageDir string, contents repository.PackageResources) { +func WritePackage(t *testing.T, packageDir string, contents PackageResources) { for k, v := range contents.Contents { abs := filepath.Join(packageDir, k) dir := filepath.Dir(abs) diff --git a/pkg/engine/update.go b/pkg/repository/update.go similarity index 78% rename from pkg/engine/update.go rename to pkg/repository/update.go index 633f02b9..0843e2f6 100644 --- a/pkg/engine/update.go +++ b/pkg/repository/update.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" @@ -22,57 +22,56 @@ import ( "path/filepath" "github.com/nephio-project/porch/internal/kpt/util/update" - "github.com/nephio-project/porch/pkg/repository" ) // defaultPackageUpdater implements packageUpdater interface. -type defaultPackageUpdater struct{} +type DefaultPackageUpdater struct{} -func (m *defaultPackageUpdater) Update( +func (m *DefaultPackageUpdater) Update( ctx context.Context, localResources, originalResources, - upstreamResources repository.PackageResources) (updatedResources repository.PackageResources, err error) { + upstreamResources PackageResources) (updatedResources PackageResources, err error) { localDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(localDir) originalDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(originalDir) upstreamDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(upstreamDir) if err := writeResourcesToDirectory(localDir, localResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := writeResourcesToDirectory(originalDir, originalResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := writeResourcesToDirectory(upstreamDir, upstreamResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := m.do(ctx, localDir, originalDir, upstreamDir); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } return loadResourcesFromDirectory(localDir) } // PkgUpdate is a wrapper around `kpt pkg update`, running it against the package in packageDir -func (m *defaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDir, upstreamPkgDir string) error { +func (m *DefaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDir, upstreamPkgDir string) error { relPath := "." localPath := filepath.Join(localPkgDir, relPath) updatedPath := filepath.Join(upstreamPkgDir, relPath) @@ -94,7 +93,7 @@ func (m *defaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDi return nil } -func writeResourcesToDirectory(dir string, resources repository.PackageResources) error { +func writeResourcesToDirectory(dir string, resources PackageResources) error { for k, v := range resources.Contents { p := filepath.Join(dir, k) dir := filepath.Dir(p) @@ -108,9 +107,9 @@ func writeResourcesToDirectory(dir string, resources repository.PackageResources return nil } -func loadResourcesFromDirectory(dir string) (repository.PackageResources, error) { +func loadResourcesFromDirectory(dir string) (PackageResources, error) { // TODO: return abstraction instead of loading everything - result := repository.PackageResources{ + result := PackageResources{ Contents: map[string]string{}, } if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { @@ -132,7 +131,7 @@ func loadResourcesFromDirectory(dir string) (repository.PackageResources, error) result.Contents[rel] = string(contents) return nil }); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } return result, nil diff --git a/pkg/engine/update_test.go b/pkg/repository/update_test.go similarity index 97% rename from pkg/engine/update_test.go rename to pkg/repository/update_test.go index 4acac1ec..e10a25d1 100644 --- a/pkg/engine/update_test.go +++ b/pkg/repository/update_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" @@ -23,7 +23,7 @@ import ( ) func TestPkgUpdate(t *testing.T) { - dfUpdater := &defaultPackageUpdater{} + dfUpdater := &DefaultPackageUpdater{} testdata, err := filepath.Abs(filepath.Join(".", "testdata", "update")) if err != nil { diff --git a/pkg/engine/builtin.go b/pkg/task/builtin.go similarity index 99% rename from pkg/engine/builtin.go rename to pkg/task/builtin.go index ff696a07..8f93daa1 100644 --- a/pkg/engine/builtin.go +++ b/pkg/task/builtin.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/builtin_test.go b/pkg/task/builtin_test.go similarity index 87% rename from pkg/engine/builtin_test.go rename to pkg/task/builtin_test.go index 10342528..29dd5181 100644 --- a/pkg/engine/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -22,6 +22,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/pkg/repository" ) const ( @@ -34,7 +35,7 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to find testdata: %v", err) } - input := readPackage(t, filepath.Join(testdata, "input")) + input := repository.ReadPackage(t, filepath.Join(testdata, "input")) packageConfig := &builtins.PackageConfig{ PackagePath: "parent1/parent1.2/parent1.2.3/me", @@ -56,10 +57,10 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to update golden files: %v", err) } - writePackage(t, expectedPackage, got) + repository.WritePackage(t, expectedPackage, got) } - want := readPackage(t, expectedPackage) + want := repository.ReadPackage(t, expectedPackage) if !cmp.Equal(want, got) { t.Errorf("Unexpected result of builtin function mutation (-want, +got): %s", cmp.Diff(want, got)) diff --git a/pkg/engine/clone.go b/pkg/task/clone.go similarity index 93% rename from pkg/engine/clone.go rename to pkg/task/clone.go index 0bc32e59..6bd35c00 100644 --- a/pkg/engine/clone.go +++ b/pkg/task/clone.go @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" "errors" "fmt" "os" - "strings" api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" @@ -41,9 +40,9 @@ type clonePackageMutation struct { name string // package target name isDeployment bool // is the package deployable instance - repoOpener RepositoryOpener + repoOpener repository.RepositoryOpener credentialResolver repository.CredentialResolver - referenceResolver ReferenceResolver + referenceResolver repository.ReferenceResolver // packageConfig contains the package configuration. packageConfig *builtins.PackageConfig @@ -107,9 +106,9 @@ func (m *clonePackageMutation) cloneFromRegisteredRepository(ctx context.Context return repository.PackageResources{}, fmt.Errorf("upstreamRef.name is required") } - upstreamRevision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, }).FetchRevision(ctx, ref, m.namespace) if err != nil { return repository.PackageResources{}, fmt.Errorf("failed to fetch package revision %q: %w", ref.Name, err) @@ -196,11 +195,3 @@ func (m *clonePackageMutation) cloneFromGit(ctx context.Context, gitPackage *api func (m *clonePackageMutation) cloneFromOci(_ context.Context, _ *api.OciPackage) (repository.PackageResources, error) { return repository.PackageResources{}, errors.New("clone from OCI is not implemented") } - -func parseUpstreamRepository(name string) (string, error) { - lastDash := strings.LastIndex(name, "-") - if lastDash < 0 { - return "", fmt.Errorf("malformed package revision name; expected at least one hyphen: %q", name) - } - return name[:lastDash], nil -} diff --git a/pkg/engine/clone_test.go b/pkg/task/clone_test.go similarity index 99% rename from pkg/engine/clone_test.go rename to pkg/task/clone_test.go index fc715260..d6f92453 100644 --- a/pkg/engine/clone_test.go +++ b/pkg/task/clone_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/edit.go b/pkg/task/edit.go similarity index 90% rename from pkg/engine/edit.go rename to pkg/task/edit.go index 0578efcb..f6cd645d 100644 --- a/pkg/engine/edit.go +++ b/pkg/task/edit.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -28,8 +28,8 @@ type editPackageMutation struct { namespace string repositoryName string packageName string - repoOpener RepositoryOpener - referenceResolver ReferenceResolver + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver } var _ mutation = &editPackageMutation{} @@ -40,9 +40,9 @@ func (m *editPackageMutation) Apply(ctx context.Context, resources repository.Pa sourceRef := m.task.Edit.Source - revision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, + revision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, }).FetchRevision(ctx, sourceRef, m.namespace) if err != nil { return repository.PackageResources{}, nil, fmt.Errorf("failed to fetch package %q: %w", sourceRef.Name, err) diff --git a/pkg/engine/edit_test.go b/pkg/task/edit_test.go similarity index 94% rename from pkg/engine/edit_test.go rename to pkg/task/edit_test.go index 0e46163f..1c48d15d 100644 --- a/pkg/engine/edit_test.go +++ b/pkg/task/edit_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -22,9 +22,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/engine/fake" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" ) func TestEdit(t *testing.T) { @@ -32,7 +32,7 @@ func TestEdit(t *testing.T) { packageName := "repo-1234567890" repositoryName := "repo" revision := "v1" - packageRevision := &fake.PackageRevision{ + packageRevision := &fake.FakePackageRevision{ Name: packageName, PackageRevisionKey: repository.PackageRevisionKey{ Package: pkg, @@ -110,7 +110,7 @@ info: // Implementation of the ReferenceResolver interface for testing. type fakeReferenceResolver struct{} -func (f *fakeReferenceResolver) ResolveReference(ctx context.Context, namespace, name string, result Object) error { +func (f *fakeReferenceResolver) ResolveReference(ctx context.Context, namespace, name string, result repository.Object) error { return nil } diff --git a/pkg/engine/eval.go b/pkg/task/eval.go similarity index 99% rename from pkg/engine/eval.go rename to pkg/task/eval.go index db2b4e1f..3bbf68b3 100644 --- a/pkg/engine/eval.go +++ b/pkg/task/eval.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/init.go b/pkg/task/init.go similarity index 99% rename from pkg/engine/init.go rename to pkg/task/init.go index 8b88a77e..a279f732 100644 --- a/pkg/engine/init.go +++ b/pkg/task/init.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/init_test.go b/pkg/task/init_test.go similarity index 99% rename from pkg/engine/init_test.go rename to pkg/task/init_test.go index c5850858..0593f1aa 100644 --- a/pkg/engine/init_test.go +++ b/pkg/task/init_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/kio.go b/pkg/task/kio.go similarity index 99% rename from pkg/engine/kio.go rename to pkg/task/kio.go index 8b1740f2..6b253a72 100644 --- a/pkg/engine/kio.go +++ b/pkg/task/kio.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/mergekey.go b/pkg/task/mergekey.go similarity index 99% rename from pkg/engine/mergekey.go rename to pkg/task/mergekey.go index c3327c3d..93d97579 100644 --- a/pkg/engine/mergekey.go +++ b/pkg/task/mergekey.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/engine_test.go b/pkg/task/patch_test.go similarity index 96% rename from pkg/engine/engine_test.go rename to pkg/task/patch_test.go index 0248d6bf..eb116388 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/task/patch_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -21,9 +21,9 @@ import ( "github.com/google/go-cmp/cmp" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine/fake" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" ) func TestSomething(t *testing.T) { @@ -34,7 +34,7 @@ func TestSomething(t *testing.T) { patch api.PatchSpec }{ "no gates or conditions": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{}, }, newApiPkgRev: &api.PackageRevision{ @@ -43,7 +43,7 @@ func TestSomething(t *testing.T) { hasPatch: false, }, "first gate and condition added": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{}, }, newApiPkgRev: &api.PackageRevision{ @@ -83,7 +83,7 @@ func TestSomething(t *testing.T) { }, }, "additional readinessGates and conditions added": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ @@ -156,7 +156,7 @@ func TestSomething(t *testing.T) { }, }, "no changes": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ @@ -217,7 +217,7 @@ func TestSomething(t *testing.T) { hasPatch: false, }, "readinessGates and conditions removed": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ diff --git a/pkg/engine/patchgen.go b/pkg/task/patchgen.go similarity index 99% rename from pkg/engine/patchgen.go rename to pkg/task/patchgen.go index b09fb01f..e27b14ed 100644 --- a/pkg/engine/patchgen.go +++ b/pkg/task/patchgen.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/patchgen_test.go b/pkg/task/patchgen_test.go similarity index 99% rename from pkg/engine/patchgen_test.go rename to pkg/task/patchgen_test.go index 9432d9a4..57aaeacc 100644 --- a/pkg/engine/patchgen_test.go +++ b/pkg/task/patchgen_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/render.go b/pkg/task/render.go similarity index 99% rename from pkg/engine/render.go rename to pkg/task/render.go index 6219b054..31d5744b 100644 --- a/pkg/engine/render.go +++ b/pkg/task/render.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/render_test.go b/pkg/task/render_test.go similarity index 99% rename from pkg/engine/render_test.go rename to pkg/task/render_test.go index 745491ce..bf24b6b1 100644 --- a/pkg/engine/render_test.go +++ b/pkg/task/render_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/replace_test.go b/pkg/task/replace_test.go similarity index 96% rename from pkg/engine/replace_test.go rename to pkg/task/replace_test.go index a3c9fe50..fc29a4cc 100644 --- a/pkg/engine/replace_test.go +++ b/pkg/task/replace_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" @@ -30,7 +30,7 @@ import ( func TestReplaceResources(t *testing.T) { ctx := context.Background() - input := readPackage(t, filepath.Join("testdata", "replace")) + input := repository.ReadPackage(t, filepath.Join("testdata", "replace")) nocomment := removeComments(t, input) replace := &mutationReplaceResources{ diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go new file mode 100644 index 00000000..5ea48726 --- /dev/null +++ b/pkg/task/taskhandler.go @@ -0,0 +1,675 @@ +// Copyright 2022,2024 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package task + +import ( + "bytes" + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/internal/kpt/fnruntime" + "github.com/nephio-project/porch/pkg/kpt" + kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/kpt/fn" + "github.com/nephio-project/porch/pkg/repository" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/kustomize/kyaml/comments" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var tracer = otel.Tracer("task") + +type TaskHandler struct { + RunnerOptionsResolver func(namespace string) fnruntime.RunnerOptions + Runtime fn.FunctionRuntime + RepoOpener repository.RepositoryOpener + CredentialResolver repository.CredentialResolver + ReferenceResolver repository.ReferenceResolver +} + +type mutation interface { + Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) +} + +func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { + for _, tt := range oneOf { + if taskType == tt { + return true + } + } + return false +} + +func (th *TaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { + var mutations []mutation + + // Unless first task is Init or Clone, insert Init to create an empty package. + tasks := obj.Spec.Tasks + if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { + mutations = append(mutations, &initPackageMutation{ + name: obj.Spec.PackageName, + task: &api.Task{ + Init: &api.PackageInitTaskSpec{ + Subpackage: "", + Description: fmt.Sprintf("%s description", obj.Spec.PackageName), + }, + }, + }) + } + + for i := range tasks { + task := &tasks[i] + mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) + if err != nil { + return err + } + mutations = append(mutations, mutation) + } + + // Render package after creation. + mutations = th.conditionalAddRender(obj, mutations) + + baseResources := repository.PackageResources{} + if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { + return err + } + + return nil +} + +func (th *TaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { + switch task.Type { + case api.TaskTypeInit: + if task.Init == nil { + return nil, fmt.Errorf("init not set for task of type %q", task.Type) + } + return &initPackageMutation{ + name: obj.Spec.PackageName, + task: task, + }, nil + case api.TaskTypeClone: + if task.Clone == nil { + return nil, fmt.Errorf("clone not set for task of type %q", task.Type) + } + return &clonePackageMutation{ + task: task, + namespace: obj.Namespace, + name: obj.Spec.PackageName, + isDeployment: isDeployment, + repoOpener: th.RepoOpener, + credentialResolver: th.CredentialResolver, + referenceResolver: th.ReferenceResolver, + packageConfig: packageConfig, + }, nil + + case api.TaskTypeUpdate: + if task.Update == nil { + return nil, fmt.Errorf("update not set for task of type %q", task.Type) + } + cloneTask := findCloneTask(obj) + if cloneTask == nil { + return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) + } + return &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: task, + namespace: obj.Namespace, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + pkgName: obj.Spec.PackageName, + }, nil + + case api.TaskTypePatch: + return buildPatchMutation(ctx, task) + + case api.TaskTypeEdit: + if task.Edit == nil { + return nil, fmt.Errorf("edit not set for task of type %q", task.Type) + } + return &editPackageMutation{ + task: task, + namespace: obj.Namespace, + packageName: obj.Spec.PackageName, + repositoryName: obj.Spec.RepositoryName, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + }, nil + + case api.TaskTypeEval: + if task.Eval == nil { + return nil, fmt.Errorf("eval not set for task of type %q", task.Type) + } + // TODO: We should find a different way to do this. Probably a separate + // task for render. + if task.Eval.Image == "render" { + runnerOptions := th.RunnerOptionsResolver(obj.Namespace) + return &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }, nil + } else { + runnerOptions := th.RunnerOptionsResolver(obj.Namespace) + return &evalFunctionMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + task: task, + }, nil + } + + default: + return nil, fmt.Errorf("task of type %q not supported", task.Type) + } +} + +func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { + kf, err := oldPackage.GetKptfile(ctx) + if err != nil { + return nil, false, err + } + + var orgKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + orgKfString = buf.String() + } + + var readinessGates []kptfile.ReadinessGate + for _, rg := range newObj.Spec.ReadinessGates { + readinessGates = append(readinessGates, kptfile.ReadinessGate{ + ConditionType: rg.ConditionType, + }) + } + + var conditions []kptfile.Condition + for _, c := range newObj.Status.Conditions { + conditions = append(conditions, kptfile.Condition{ + Type: c.Type, + Status: convertStatusToKptfile(c.Status), + Reason: c.Reason, + Message: c.Message, + }) + } + + if kf.Info == nil && len(readinessGates) > 0 { + kf.Info = &kptfile.PackageInfo{} + } + if len(readinessGates) > 0 { + kf.Info.ReadinessGates = readinessGates + } + + if kf.Status == nil && len(conditions) > 0 { + kf.Status = &kptfile.Status{} + } + if len(conditions) > 0 { + kf.Status.Conditions = conditions + } + + var newKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + newKfString = buf.String() + } + patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) + if err != nil { + return nil, false, err + } + // If patch is empty, don't create a Task. + if patchSpec.Contents == "" { + return nil, false, nil + } + + return &api.Task{ + Type: api.TaskTypePatch, + Patch: &api.PackagePatchTaskSpec{ + Patches: []api.PatchSpec{ + patchSpec, + }, + }, + }, true, nil +} + +func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { + switch s { + case api.ConditionTrue: + return kptfile.ConditionTrue + case api.ConditionFalse: + return kptfile.ConditionFalse + case api.ConditionUnknown: + return kptfile.ConditionUnknown + default: + panic(fmt.Errorf("unknown condition status: %v", s)) + } +} + +// conditionalAddRender adds a render mutation to the end of the mutations slice if the last +// entry is not already a render mutation. +func (th *TaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { + if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { + return mutations + } + + runnerOptions := th.RunnerOptionsResolver(subject.GetNamespace()) + + return append(mutations, &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }) +} + +func isRenderMutation(m mutation) bool { + _, isRender := m.(*renderPackageMutation) + return isRender +} + +// applyResourceMutations mutates the resources and returns the most recent renderResult. +func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { + var lastApplied mutation + for _, m := range mutations { + updatedResources, taskResult, err := m.Apply(ctx, baseResources) + if taskResult == nil && err == nil { + // a nil taskResult means nothing changed + continue + } + + var task *api.Task + if taskResult != nil { + task = taskResult.Task + } + if taskResult != nil && task.Type == api.TaskTypeEval { + renderStatus = taskResult.RenderStatus + } + if err != nil { + return updatedResources, renderStatus, err + } + + // if the last applied mutation was a render mutation, and so is this one, skip it + if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { + continue + } + lastApplied = m + + if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ + Spec: api.PackageRevisionResourcesSpec{ + Resources: updatedResources.Contents, + }, + }, task); err != nil { + return updatedResources, renderStatus, err + } + baseResources = updatedResources + applied = updatedResources + } + + return applied, renderStatus, nil +} + +type updatePackageMutation struct { + cloneTask *api.Task + updateTask *api.Task + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver + namespace string + pkgName string +} + +func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) + defer span.End() + + currUpstreamPkgRef, err := m.currUpstream() + if err != nil { + return repository.PackageResources{}, nil, err + } + + targetUpstream := m.updateTask.Update.Upstream + if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { + return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") + } + + originalResources, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", + m.pkgName, *currUpstreamPkgRef) + } + + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) + } + upstreamResources, err := upstreamRevision.GetResources(ctx) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) + } + + klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", + m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) + + // May be have packageUpdater part of the Porch core to make it easy for testing ? + updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, + resources, + repository.PackageResources{ + Contents: originalResources.Spec.Resources, + }, + repository.PackageResources{ + Contents: upstreamResources.Spec.Resources, + }) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) + } + + newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) + } + if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) + } + + // ensure merge-key comment is added to newly added resources. + result, err := ensureMergeKey(ctx, updatedResources) + if err != nil { + klog.Infof("failed to add merge key comments: %v", err) + } + return result, &api.TaskResult{Task: m.updateTask}, nil +} + +// Currently assumption is that downstream packages will be forked from a porch package. +// As per current implementation, upstream package ref is stored in a new update task but this may +// change so the logic of figuring out current upstream will live in this function. +func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { + if m.cloneTask == nil || m.cloneTask.Clone == nil { + return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) + } + upstream := m.cloneTask.Clone.Upstream + if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { + return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) + } + return upstream.UpstreamRef, nil +} + +func findCloneTask(pr *api.PackageRevision) *api.Task { + if len(pr.Spec.Tasks) == 0 { + return nil + } + firstTask := pr.Spec.Tasks[0] + if firstTask.Type == api.TaskTypeClone { + return &firstTask + } + return nil +} + +type mutationReplaceResources struct { + newResources *api.PackageRevisionResources + oldResources *api.PackageRevisionResources +} + +func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) + defer span.End() + + patch := &api.PackagePatchTaskSpec{} + + old := resources.Contents + new, err := healConfig(old, m.newResources.Spec.Resources) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) + } + + for k, newV := range new { + oldV, ok := old[k] + // New config or changed config + if !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeCreateFile, + Contents: newV, + } + patch.Patches = append(patch.Patches, patchSpec) + } else if newV != oldV { + patchSpec, err := GeneratePatch(k, oldV, newV) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) + } + if patchSpec.Contents == "" { + continue + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + for k := range old { + // Deleted config + if _, ok := new[k]; !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeDeleteFile, + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + // If patch is empty, don't create a Task. + var taskResult *api.TaskResult + if len(patch.Patches) > 0 { + taskResult = &api.TaskResult{ + Task: &api.Task{ + Type: api.TaskTypePatch, + Patch: patch, + }, + } + } + return repository.PackageResources{Contents: new}, taskResult, nil +} + +func healConfig(old, new map[string]string) (map[string]string, error) { + // Copy comments from old config to new + oldResources, err := (&packageReader{ + input: repository.PackageResources{Contents: old}, + extra: map[string]string{}, + }).Read() + if err != nil { + return nil, fmt.Errorf("failed to read old packge resources: %w", err) + } + + var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { + for _, n := range r { + for _, original := range oldResources { + if n.GetNamespace() == original.GetNamespace() && + n.GetName() == original.GetName() && + n.GetApiVersion() == original.GetApiVersion() && + n.GetKind() == original.GetKind() { + comments.CopyComments(original, n) + } + } + } + return r, nil + } + + out := &packageWriter{ + output: repository.PackageResources{ + Contents: map[string]string{}, + }, + } + + extra := map[string]string{} + + if err := (kio.Pipeline{ + Inputs: []kio.Reader{&packageReader{ + input: repository.PackageResources{Contents: new}, + extra: extra, + }}, + Filters: []kio.Filter{filter}, + Outputs: []kio.Writer{out}, + ContinueOnEmptyResult: true, + }).Execute(); err != nil { + return nil, err + } + + healed := out.output.Contents + + for k, v := range extra { + healed[k] = v + } + + return healed, nil +} + +func (th *TaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { + ctx, span := tracer.Start(ctx, "TaskHandler::DoPRMutations", trace.WithAttributes()) + defer span.End() + + var mutations []mutation + if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { + return fmt.Errorf("removing tasks is not yet supported") + } + for i := range oldObj.Spec.Tasks { + oldTask := &oldObj.Spec.Tasks[i] + newTask := &newObj.Spec.Tasks[i] + if oldTask.Type != newTask.Type { + return fmt.Errorf("changing task types is not yet supported") + } + } + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { + return fmt.Errorf("can only append one task at a time") + } + + newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] + if newTask.Type != api.TaskTypeUpdate { + return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) + } + if newTask.Update == nil { + return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) + } + + cloneTask := findCloneTask(oldObj) + if cloneTask == nil { + return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) + } + + mutation := &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: &newTask, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + namespace: namespace, + pkgName: oldObj.GetName(), + } + mutations = append(mutations, mutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // If any of the fields in the API that are projections from the Kptfile + // must be updated in the Kptfile as well. + kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) + if err != nil { + return err + } + if created { + kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) + if err != nil { + return err + } + mutations = append(mutations, kfPatchMutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // TODO: Handle the case if alongside lifecycle change, tasks are changed too. + // Update package contents only if the package is in draft state + if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { + apiResources, err := repoPR.GetResources(ctx) + if err != nil { + return fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: apiResources.Spec.Resources, + } + + if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { + return err + } + } + + return nil +} + +func (th *TaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { + ctx, span := tracer.Start(ctx, "TaskHandler::DoPRResourceMutations", trace.WithAttributes()) + defer span.End() + + runnerOptions := th.RunnerOptionsResolver(oldRes.GetNamespace()) + + mutations := []mutation{ + &mutationReplaceResources{ + newResources: newRes, + oldResources: oldRes, + }, + } + prevResources, err := pr2Update.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: prevResources.Spec.Resources, + } + + appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) + if err != nil { + return nil, err + } + + var renderStatus *api.RenderStatus + if len(appliedResources.Contents) > 0 { + // render the package + // Render failure will not fail the overall API operation. + // The render error and result is captured as part of renderStatus above + // and is returned in packageresourceresources API's status field. We continue with + // saving the non-rendered resources to avoid losing user's changes. + // and supress this err. + _, renderStatus, _ = applyResourceMutations(ctx, + draft, + appliedResources, + []mutation{&renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }}) + } else { + renderStatus = nil + } + + return renderStatus, nil +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 5dab0f90..c323add7 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "fmt" "os" + "strings" porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -100,3 +101,11 @@ func SchemaToMetaGVR(gvr schema.GroupVersionResource) metav1.GroupVersionResourc Resource: gvr.Resource, } } + +func ParseRepositoryName(name string) (string, error) { + lastDash := strings.LastIndex(name, "-") + if lastDash < 0 { + return "", fmt.Errorf("malformed package revision name; expected at least one hyphen: %q", name) + } + return name[:lastDash], nil +} From c0175d612bbe147f7dd0d342164339d9291d6f53 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Wed, 13 Nov 2024 18:43:40 +0000 Subject: [PATCH 02/13] Fix the metadata returned to the API on packagerevisions --- pkg/cache/memory/util.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index cdb3ca1c..5f998a72 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -62,6 +62,8 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac for _, v := range latest { v.isLatestRevision = true } + + // If the } func toPackageRevisionSlice(cached map[repository.PackageRevisionKey]*cachedPackageRevision, filter repository.ListPackageRevisionFilter) []repository.PackageRevision { From e9eacdbb3a564592bb82041651882c4faf16245a Mon Sep 17 00:00:00 2001 From: liamfallon Date: Thu, 14 Nov 2024 15:18:52 +0000 Subject: [PATCH 03/13] Rebased against main --- pkg/registry/porch/watch.go | 3 -- pkg/repository/testdata/update/local/Kptfile | 27 +++++++++++++++++ .../testdata/update/local/README.md | 21 ++++++++++++++ .../testdata/update/local/namespace.yaml | 5 ++++ .../update/local/package-context.yaml | 8 +++++ .../testdata/update/local/resourcequota.yaml | 9 ++++++ .../testdata/update/local/rolebinding.yaml | 13 +++++++++ .../update/local/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/original/Kptfile | 14 +++++++++ .../testdata/update/original/README.md | 21 ++++++++++++++ .../testdata/update/original/namespace.yaml | 5 ++++ .../update/original/package-context.yaml | 8 +++++ .../update/original/resourcequota.yaml | 9 ++++++ .../testdata/update/original/rolebinding.yaml | 13 +++++++++ .../update/original/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/updated/Kptfile | 27 +++++++++++++++++ .../testdata/update/updated/README.md | 21 ++++++++++++++ .../testdata/update/updated/namespace.yaml | 5 ++++ .../update/updated/package-context.yaml | 8 +++++ .../update/updated/resourcequota.yaml | 9 ++++++ .../testdata/update/updated/rolebinding.yaml | 13 +++++++++ .../update/updated/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/upstream/Kptfile | 14 +++++++++ .../testdata/update/upstream/README.md | 21 ++++++++++++++ .../testdata/update/upstream/namespace.yaml | 5 ++++ .../update/upstream/package-context.yaml | 8 +++++ .../update/upstream/resourcequota.yaml | 9 ++++++ .../testdata/update/upstream/rolebinding.yaml | 13 +++++++++ .../update/upstream/update-rolebinding.yaml | 20 +++++++++++++ pkg/task/testdata/clone/bucket/Kptfile | 20 +++++++++++++ pkg/task/testdata/clone/bucket/bucket.yaml | 26 +++++++++++++++++ pkg/task/testdata/clone/configmap/Kptfile | 20 +++++++++++++ .../testdata/clone/configmap/configmap.yaml | 21 ++++++++++++++ pkg/task/testdata/context/expected/Kptfile | 6 ++++ .../testdata/context/expected/bucket.yaml | 12 ++++++++ .../context/expected/package-context.yaml | 9 ++++++ pkg/task/testdata/context/input/Kptfile | 6 ++++ pkg/task/testdata/context/input/bucket.yaml | 12 ++++++++ pkg/task/testdata/init/testpkg/Kptfile | 13 +++++++++ pkg/task/testdata/init/testpkg/README.md | 21 ++++++++++++++ .../init/testpkg/package-context.yaml | 8 +++++ pkg/task/testdata/replace/Kptfile | 23 +++++++++++++++ pkg/task/testdata/replace/README.md | 1 + pkg/task/testdata/replace/bucket.yaml | 18 ++++++++++++ pkg/task/testdata/simple-render/expected.txt | 27 +++++++++++++++++ .../simple-render/simple-bucket/Kptfile | 29 +++++++++++++++++++ .../simple-render/simple-bucket/bucket.yaml | 26 +++++++++++++++++ 47 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 pkg/repository/testdata/update/local/Kptfile create mode 100644 pkg/repository/testdata/update/local/README.md create mode 100644 pkg/repository/testdata/update/local/namespace.yaml create mode 100644 pkg/repository/testdata/update/local/package-context.yaml create mode 100644 pkg/repository/testdata/update/local/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/local/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/local/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/original/Kptfile create mode 100644 pkg/repository/testdata/update/original/README.md create mode 100644 pkg/repository/testdata/update/original/namespace.yaml create mode 100644 pkg/repository/testdata/update/original/package-context.yaml create mode 100644 pkg/repository/testdata/update/original/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/original/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/original/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/updated/Kptfile create mode 100644 pkg/repository/testdata/update/updated/README.md create mode 100644 pkg/repository/testdata/update/updated/namespace.yaml create mode 100644 pkg/repository/testdata/update/updated/package-context.yaml create mode 100644 pkg/repository/testdata/update/updated/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/updated/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/updated/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/upstream/Kptfile create mode 100644 pkg/repository/testdata/update/upstream/README.md create mode 100644 pkg/repository/testdata/update/upstream/namespace.yaml create mode 100644 pkg/repository/testdata/update/upstream/package-context.yaml create mode 100644 pkg/repository/testdata/update/upstream/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/upstream/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/upstream/update-rolebinding.yaml create mode 100644 pkg/task/testdata/clone/bucket/Kptfile create mode 100644 pkg/task/testdata/clone/bucket/bucket.yaml create mode 100644 pkg/task/testdata/clone/configmap/Kptfile create mode 100644 pkg/task/testdata/clone/configmap/configmap.yaml create mode 100644 pkg/task/testdata/context/expected/Kptfile create mode 100644 pkg/task/testdata/context/expected/bucket.yaml create mode 100644 pkg/task/testdata/context/expected/package-context.yaml create mode 100644 pkg/task/testdata/context/input/Kptfile create mode 100644 pkg/task/testdata/context/input/bucket.yaml create mode 100644 pkg/task/testdata/init/testpkg/Kptfile create mode 100644 pkg/task/testdata/init/testpkg/README.md create mode 100644 pkg/task/testdata/init/testpkg/package-context.yaml create mode 100644 pkg/task/testdata/replace/Kptfile create mode 100644 pkg/task/testdata/replace/README.md create mode 100644 pkg/task/testdata/replace/bucket.yaml create mode 100644 pkg/task/testdata/simple-render/expected.txt create mode 100644 pkg/task/testdata/simple-render/simple-bucket/Kptfile create mode 100644 pkg/task/testdata/simple-render/simple-bucket/bucket.yaml diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index 3b721046..4f5de58f 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -238,9 +238,6 @@ func (w *watcher) sendWatchEvent(ev watch.Event) { // TODO: Handle the case that the watch channel is full? w.resultChan <- ev w.totalSent += 1 - if (w.totalSent % 100) == 0 { - klog.Infof("watch %p: total sent: %d", w, w.totalSent) - } } // OnPackageRevisionChange is the callback called when a PackageRevision changes. diff --git a/pkg/repository/testdata/update/local/Kptfile b/pkg/repository/testdata/update/local/Kptfile new file mode 100644 index 00000000..56adbced --- /dev/null +++ b/pkg/repository/testdata/update/local/Kptfile @@ -0,0 +1,27 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: backend + annotations: + config.kubernetes.io/local-config: "true" +upstream: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 +upstreamLock: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 + commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/local/README.md b/pkg/repository/testdata/update/local/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/local/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/local/namespace.yaml b/pkg/repository/testdata/update/local/namespace.yaml new file mode 100644 index 00000000..5a044679 --- /dev/null +++ b/pkg/repository/testdata/update/local/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: # kpt-merge: example + name: backend +spec: {} diff --git a/pkg/repository/testdata/update/local/package-context.yaml b/pkg/repository/testdata/update/local/package-context.yaml new file mode 100644 index 00000000..9b77f45e --- /dev/null +++ b/pkg/repository/testdata/update/local/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: backend diff --git a/pkg/repository/testdata/update/local/resourcequota.yaml b/pkg/repository/testdata/update/local/resourcequota.yaml new file mode 100644 index 00000000..02e22b08 --- /dev/null +++ b/pkg/repository/testdata/update/local/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: # kpt-merge: example/default + name: default + namespace: backend +spec: + hard: + cpu: "40" + memory: 40G diff --git a/pkg/repository/testdata/update/local/rolebinding.yaml b/pkg/repository/testdata/update/local/rolebinding.yaml new file mode 100644 index 00000000..f57f1f2e --- /dev/null +++ b/pkg/repository/testdata/update/local/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: # kpt-merge: example/app-admin + name: app-admin + namespace: backend +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: backend.admin@bigco.com diff --git a/pkg/repository/testdata/update/local/update-rolebinding.yaml b/pkg/repository/testdata/update/local/update-rolebinding.yaml new file mode 100644 index 00000000..6fdff582 --- /dev/null +++ b/pkg/repository/testdata/update/local/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: # kpt-merge: example/update-rolebinding + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/original/Kptfile b/pkg/repository/testdata/update/original/Kptfile new file mode 100644 index 00000000..db627135 --- /dev/null +++ b/pkg/repository/testdata/update/original/Kptfile @@ -0,0 +1,14 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: basens + annotations: + config.kubernetes.io/local-config: "true" +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/original/README.md b/pkg/repository/testdata/update/original/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/original/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/original/namespace.yaml b/pkg/repository/testdata/update/original/namespace.yaml new file mode 100644 index 00000000..9219ae32 --- /dev/null +++ b/pkg/repository/testdata/update/original/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: example +spec: {} diff --git a/pkg/repository/testdata/update/original/package-context.yaml b/pkg/repository/testdata/update/original/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/repository/testdata/update/original/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/repository/testdata/update/original/resourcequota.yaml b/pkg/repository/testdata/update/original/resourcequota.yaml new file mode 100644 index 00000000..a5c7f9dc --- /dev/null +++ b/pkg/repository/testdata/update/original/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: default + namespace: example +spec: + hard: + cpu: "40" + memory: 40G diff --git a/pkg/repository/testdata/update/original/rolebinding.yaml b/pkg/repository/testdata/update/original/rolebinding.yaml new file mode 100644 index 00000000..12ce8875 --- /dev/null +++ b/pkg/repository/testdata/update/original/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: app-admin + namespace: example +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: example.admin@bigco.com diff --git a/pkg/repository/testdata/update/original/update-rolebinding.yaml b/pkg/repository/testdata/update/original/update-rolebinding.yaml new file mode 100644 index 00000000..a20edf50 --- /dev/null +++ b/pkg/repository/testdata/update/original/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/updated/Kptfile b/pkg/repository/testdata/update/updated/Kptfile new file mode 100644 index 00000000..56adbced --- /dev/null +++ b/pkg/repository/testdata/update/updated/Kptfile @@ -0,0 +1,27 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: backend + annotations: + config.kubernetes.io/local-config: "true" +upstream: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 +upstreamLock: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 + commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/updated/README.md b/pkg/repository/testdata/update/updated/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/updated/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/updated/namespace.yaml b/pkg/repository/testdata/update/updated/namespace.yaml new file mode 100644 index 00000000..5a044679 --- /dev/null +++ b/pkg/repository/testdata/update/updated/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: # kpt-merge: example + name: backend +spec: {} diff --git a/pkg/repository/testdata/update/updated/package-context.yaml b/pkg/repository/testdata/update/updated/package-context.yaml new file mode 100644 index 00000000..9b77f45e --- /dev/null +++ b/pkg/repository/testdata/update/updated/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: backend diff --git a/pkg/repository/testdata/update/updated/resourcequota.yaml b/pkg/repository/testdata/update/updated/resourcequota.yaml new file mode 100644 index 00000000..e7bc953e --- /dev/null +++ b/pkg/repository/testdata/update/updated/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: # kpt-merge: example/default + name: default + namespace: backend +spec: + hard: + cpu: "40" + memory: 60G diff --git a/pkg/repository/testdata/update/updated/rolebinding.yaml b/pkg/repository/testdata/update/updated/rolebinding.yaml new file mode 100644 index 00000000..f57f1f2e --- /dev/null +++ b/pkg/repository/testdata/update/updated/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: # kpt-merge: example/app-admin + name: app-admin + namespace: backend +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: backend.admin@bigco.com diff --git a/pkg/repository/testdata/update/updated/update-rolebinding.yaml b/pkg/repository/testdata/update/updated/update-rolebinding.yaml new file mode 100644 index 00000000..6fdff582 --- /dev/null +++ b/pkg/repository/testdata/update/updated/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: # kpt-merge: example/update-rolebinding + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/upstream/Kptfile b/pkg/repository/testdata/update/upstream/Kptfile new file mode 100644 index 00000000..65fe6a65 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/Kptfile @@ -0,0 +1,14 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: basens + annotations: + config.kubernetes.io/local-config: "true" +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/upstream/README.md b/pkg/repository/testdata/update/upstream/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/upstream/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/upstream/namespace.yaml b/pkg/repository/testdata/update/upstream/namespace.yaml new file mode 100644 index 00000000..9219ae32 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: example +spec: {} diff --git a/pkg/repository/testdata/update/upstream/package-context.yaml b/pkg/repository/testdata/update/upstream/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/repository/testdata/update/upstream/resourcequota.yaml b/pkg/repository/testdata/update/upstream/resourcequota.yaml new file mode 100644 index 00000000..5a56078d --- /dev/null +++ b/pkg/repository/testdata/update/upstream/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: default + namespace: example +spec: + hard: + cpu: "40" + memory: 60G diff --git a/pkg/repository/testdata/update/upstream/rolebinding.yaml b/pkg/repository/testdata/update/upstream/rolebinding.yaml new file mode 100644 index 00000000..12ce8875 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: app-admin + namespace: example +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: example.admin@bigco.com diff --git a/pkg/repository/testdata/update/upstream/update-rolebinding.yaml b/pkg/repository/testdata/update/upstream/update-rolebinding.yaml new file mode 100644 index 00000000..a20edf50 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/task/testdata/clone/bucket/Kptfile b/pkg/task/testdata/clone/bucket/Kptfile new file mode 100644 index 00000000..e2b651e4 --- /dev/null +++ b/pkg/task/testdata/clone/bucket/Kptfile @@ -0,0 +1,20 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: bucket +info: + description: Bucket test package diff --git a/pkg/task/testdata/clone/bucket/bucket.yaml b/pkg/task/testdata/clone/bucket/bucket.yaml new file mode 100644 index 00000000..9b75ffcb --- /dev/null +++ b/pkg/task/testdata/clone/bucket/bucket.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/clone/configmap/Kptfile b/pkg/task/testdata/clone/configmap/Kptfile new file mode 100644 index 00000000..54fdea56 --- /dev/null +++ b/pkg/task/testdata/clone/configmap/Kptfile @@ -0,0 +1,20 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: configmap +info: + description: ConfigMap test package diff --git a/pkg/task/testdata/clone/configmap/configmap.yaml b/pkg/task/testdata/clone/configmap/configmap.yaml new file mode 100644 index 00000000..8b4c9896 --- /dev/null +++ b/pkg/task/testdata/clone/configmap/configmap.yaml @@ -0,0 +1,21 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-name + namespace: configmap-namespace +data: + key: value diff --git a/pkg/task/testdata/context/expected/Kptfile b/pkg/task/testdata/context/expected/Kptfile new file mode 100644 index 00000000..1e92a6cf --- /dev/null +++ b/pkg/task/testdata/context/expected/Kptfile @@ -0,0 +1,6 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: input +info: + description: Builtin Function Test Package diff --git a/pkg/task/testdata/context/expected/bucket.yaml b/pkg/task/testdata/context/expected/bucket.yaml new file mode 100644 index 00000000..5dad26d4 --- /dev/null +++ b/pkg/task/testdata/context/expected/bucket.yaml @@ -0,0 +1,12 @@ +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/context/expected/package-context.yaml b/pkg/task/testdata/context/expected/package-context.yaml new file mode 100644 index 00000000..57b4e523 --- /dev/null +++ b/pkg/task/testdata/context/expected/package-context.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: input + package-path: parent1/parent1.2/parent1.2.3/me diff --git a/pkg/task/testdata/context/input/Kptfile b/pkg/task/testdata/context/input/Kptfile new file mode 100644 index 00000000..1e92a6cf --- /dev/null +++ b/pkg/task/testdata/context/input/Kptfile @@ -0,0 +1,6 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: input +info: + description: Builtin Function Test Package diff --git a/pkg/task/testdata/context/input/bucket.yaml b/pkg/task/testdata/context/input/bucket.yaml new file mode 100644 index 00000000..5dad26d4 --- /dev/null +++ b/pkg/task/testdata/context/input/bucket.yaml @@ -0,0 +1,12 @@ +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/init/testpkg/Kptfile b/pkg/task/testdata/init/testpkg/Kptfile new file mode 100644 index 00000000..7883a649 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/Kptfile @@ -0,0 +1,13 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: testpkg + annotations: + config.kubernetes.io/local-config: "true" +info: + site: http://kpt.dev/testpkg + description: test package + keywords: + - test + - kpt + - pkg diff --git a/pkg/task/testdata/init/testpkg/README.md b/pkg/task/testdata/init/testpkg/README.md new file mode 100644 index 00000000..9a34a509 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/README.md @@ -0,0 +1,21 @@ +# testpkg + +## Description +test package + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] testpkg` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree testpkg` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init testpkg +kpt live apply testpkg --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/task/testdata/init/testpkg/package-context.yaml b/pkg/task/testdata/init/testpkg/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/task/testdata/replace/Kptfile b/pkg/task/testdata/replace/Kptfile new file mode 100644 index 00000000..9529df9a --- /dev/null +++ b/pkg/task/testdata/replace/Kptfile @@ -0,0 +1,23 @@ +# top comment +apiVersion: kpt.dev/v1 +# Kptfile info +info: + # Kptfile description + description: A Google Cloud Storage bucket +# Kptfile kind +kind: Kptfile +# Kptfile metadata +metadata: + annotations: + blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint + name: simple-bucket +# Kptfile pipeline +pipeline: + # Kptfile mutators + mutators: + - configMap: + name: updated-bucket-name + namespace: updated-namespace + project-id: updated-project-id + storage-class: updated-storage-class + image: gcr.io/kpt-fn/apply-setters:v0.2.0 diff --git a/pkg/task/testdata/replace/README.md b/pkg/task/testdata/replace/README.md new file mode 100644 index 00000000..6f0623fa --- /dev/null +++ b/pkg/task/testdata/replace/README.md @@ -0,0 +1 @@ +# replace test \ No newline at end of file diff --git a/pkg/task/testdata/replace/bucket.yaml b/pkg/task/testdata/replace/bucket.yaml new file mode 100644 index 00000000..935e6573 --- /dev/null +++ b/pkg/task/testdata/replace/bucket.yaml @@ -0,0 +1,18 @@ +# top comment +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +# metadata comment +metadata: # kpt-merge: config-control/blueprints-project-bucket + # annotations comment + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} + name: blueprints-project-bucket # kpt-set: ${project-id}-${name} + namespace: config-control # kpt-set: ${namespace} +# spec comment +spec: + storageClass: standard # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + # Versioning is enabled + versioning: + enabled: false diff --git a/pkg/task/testdata/simple-render/expected.txt b/pkg/task/testdata/simple-render/expected.txt new file mode 100644 index 00000000..f625b40c --- /dev/null +++ b/pkg/task/testdata/simple-render/expected.txt @@ -0,0 +1,27 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: # kpt-merge: config-control/blueprints-project-bucket + name: updated-project-id-updated-bucket-name # kpt-set: ${project-id}-${name} + namespace: updated-namespace # kpt-set: ${namespace} + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: updated-project-id # kpt-set: ${project-id} + cnrm.cloud.google.com/blueprint: 'kpt-fn' +spec: + storageClass: updated-storage-class # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/simple-render/simple-bucket/Kptfile b/pkg/task/testdata/simple-render/simple-bucket/Kptfile new file mode 100644 index 00000000..7a6d5063 --- /dev/null +++ b/pkg/task/testdata/simple-render/simple-bucket/Kptfile @@ -0,0 +1,29 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: simple-bucket + annotations: + blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint +info: + description: A Google Cloud Storage bucket +pipeline: + mutators: + - image: gcr.io/kpt-fn/apply-setters:v0.2.0 + configMap: + name: updated-bucket-name + namespace: updated-namespace + project-id: updated-project-id + storage-class: updated-storage-class diff --git a/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml b/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml new file mode 100644 index 00000000..f80f88b6 --- /dev/null +++ b/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: # kpt-merge: config-control/blueprints-project-bucket + name: blueprints-project-bucket # kpt-set: ${project-id}-${name} + namespace: config-control # kpt-set: ${namespace} + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} +spec: + storageClass: standard # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + versioning: + enabled: false From a42e86ea924261a03cb2dcc0ed694814a10f3c2d Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:20:02 +0000 Subject: [PATCH 04/13] Address comments, introduce task interface --- pkg/cache/memory/packagerevision.go | 27 - pkg/cache/memory/util.go | 2 - pkg/engine/engine.go | 5 +- pkg/engine/options.go | 28 +- pkg/repository/fake/packagerevision.go | 7 - pkg/task/builtin.go | 2 +- pkg/task/builtin_test.go | 2 +- pkg/task/clone.go | 4 +- pkg/task/clone_test.go | 4 +- pkg/task/edit.go | 2 +- pkg/task/edit_test.go | 2 +- pkg/task/eval.go | 2 +- pkg/task/generictaskhandler.go | 694 +++++++++++++++++++++++++ pkg/task/init.go | 2 +- pkg/task/init_test.go | 2 +- pkg/task/patchgen.go | 2 +- pkg/task/render.go | 2 +- pkg/task/render_test.go | 2 +- pkg/task/replace_test.go | 2 +- pkg/task/taskhandler.go | 654 +---------------------- 20 files changed, 742 insertions(+), 705 deletions(-) create mode 100644 pkg/task/generictaskhandler.go diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index aff6a6ba..0aed7cba 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -58,30 +58,3 @@ func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*api.Pa return apiPR, nil } - -func (c *cachedPackageRevision) GetCachedPackageRevision(ctx context.Context) (*api.PackageRevision, error) { - repoPkgRev, err := c.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - var isLatest bool - if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { - isLatest = true - } - repoPkgRev.Labels = c.GetMeta().Labels - if isLatest { - // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(repoPkgRev.Labels)) - for k, v := range repoPkgRev.Labels { - labels[k] = v - } - labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue - repoPkgRev.Labels = labels - } - repoPkgRev.Annotations = c.GetMeta().Annotations - repoPkgRev.Finalizers = c.GetMeta().Finalizers - repoPkgRev.OwnerReferences = c.GetMeta().OwnerReferences - repoPkgRev.DeletionTimestamp = c.GetMeta().DeletionTimestamp - - return repoPkgRev, nil -} diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index 5f998a72..cdb3ca1c 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -62,8 +62,6 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac for _, v := range latest { v.isLatestRevision = true } - - // If the } func toPackageRevisionSlice(cached map[repository.PackageRevisionKey]*cachedPackageRevision, filter repository.ListPackageRevisionFilter) []repository.PackageRevision { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 84d1f14f..6893e579 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -59,7 +59,10 @@ type CaDEngine interface { } func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { - engine := &cadEngine{} + engine := &cadEngine{ + taskHandler: task.GetDefaultTaskHandler(), + } + for _, opt := range opts { if err := opt.apply(engine); err != nil { return nil, err diff --git a/pkg/engine/options.go b/pkg/engine/options.go index fc814992..8e84f16a 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -34,7 +34,7 @@ type EngineOptionFunc func(engine *cadEngine) error var _ EngineOption = EngineOptionFunc(nil) func (f EngineOptionFunc) apply(engine *cadEngine) error { - engine.taskHandler.RepoOpener = engine + engine.taskHandler.SetRepoOpener(engine) return f(engine) } @@ -48,12 +48,12 @@ func WithCache(cache cache.Cache) EngineOption { func WithBuiltinFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { runtime := newBuiltinRuntime() - if engine.taskHandler.Runtime == nil { - engine.taskHandler.Runtime = runtime - } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.GetRuntime() == nil { + engine.taskHandler.SetRuntime(runtime) + } else if mr, ok := engine.taskHandler.GetRuntime().(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) + engine.taskHandler.SetRuntime(fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.GetRuntime(), runtime})) } return nil }) @@ -65,12 +65,12 @@ func WithGRPCFunctionRuntime(address string) EngineOption { if err != nil { return fmt.Errorf("failed to create function runtime: %w", err) } - if engine.taskHandler.Runtime == nil { - engine.taskHandler.Runtime = runtime - } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.GetRuntime() == nil { + engine.taskHandler.SetRuntime(runtime) + } else if mr, ok := engine.taskHandler.GetRuntime().(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) + engine.taskHandler.SetRuntime(fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.GetRuntime(), runtime})) } return nil }) @@ -78,14 +78,14 @@ func WithGRPCFunctionRuntime(address string) EngineOption { func WithFunctionRuntime(runtime fn.FunctionRuntime) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.Runtime = runtime + engine.taskHandler.SetRuntime(runtime) return nil }) } func WithSimpleFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.Runtime = kpt.NewSimpleFunctionRuntime() + engine.taskHandler.SetRuntime(kpt.NewSimpleFunctionRuntime()) return nil }) } @@ -96,21 +96,21 @@ func WithRunnerOptions(options fnruntime.RunnerOptions) EngineOption { func WithRunnerOptionsResolver(fn func(namespace string) fnruntime.RunnerOptions) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.RunnerOptionsResolver = fn + engine.taskHandler.SetRunnerOptionsResolver(fn) return nil }) } func WithCredentialResolver(resolver repository.CredentialResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.CredentialResolver = resolver + engine.taskHandler.SetCredentialResolver(resolver) return nil }) } func WithReferenceResolver(resolver repository.ReferenceResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.ReferenceResolver = resolver + engine.taskHandler.SetReferenceResolver(resolver) return nil }) } diff --git a/pkg/repository/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go index 6fb1fcba..187b6f0b 100644 --- a/pkg/repository/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -97,10 +97,3 @@ func (f *FakePackageRevision) GetMeta() meta.PackageRevisionMeta { func (f *FakePackageRevision) SetMeta(meta.PackageRevisionMeta) { } - -func (f *FakePackageRevision) IsLatest() bool { - return false -} - -func (f *FakePackageRevision) SetLatest(latest bool) { -} diff --git a/pkg/task/builtin.go b/pkg/task/builtin.go index 8f93daa1..d594309e 100644 --- a/pkg/task/builtin.go +++ b/pkg/task/builtin.go @@ -46,7 +46,7 @@ func newPackageContextGeneratorMutation(packageConfig *builtins.PackageConfig) ( var _ mutation = &builtinEvalMutation{} -func (m *builtinEvalMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *builtinEvalMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ff := &runtimeutil.FunctionFilter{ Run: m.runner.Run, Results: &yaml.RNode{}, diff --git a/pkg/task/builtin_test.go b/pkg/task/builtin_test.go index 29dd5181..f6725ac1 100644 --- a/pkg/task/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -45,7 +45,7 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to get builtin function mutation: %v", err) } - got, _, err := m.Apply(context.Background(), input) + got, _, err := m.apply(context.Background(), input) if err != nil { t.Fatalf("Failed to apply builtin function mutation: %v", err) } diff --git a/pkg/task/clone.go b/pkg/task/clone.go index 6bd35c00..17928f5a 100644 --- a/pkg/task/clone.go +++ b/pkg/task/clone.go @@ -48,7 +48,7 @@ type clonePackageMutation struct { packageConfig *builtins.PackageConfig } -func (m *clonePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *clonePackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "clonePackageMutation::Apply", trace.WithAttributes()) defer span.End() @@ -83,7 +83,7 @@ func (m *clonePackageMutation) Apply(ctx context.Context, resources repository.P if err != nil { return repository.PackageResources{}, nil, err } - cloned, _, err = genPkgContextMutation.Apply(ctx, cloned) + cloned, _, err = genPkgContextMutation.apply(ctx, cloned) if err != nil { return repository.PackageResources{}, nil, fmt.Errorf("failed to generate deployment context %w", err) } diff --git a/pkg/task/clone_test.go b/pkg/task/clone_test.go index d6f92453..3422b851 100644 --- a/pkg/task/clone_test.go +++ b/pkg/task/clone_test.go @@ -250,14 +250,14 @@ func TestCloneGitBasicAuth(t *testing.T) { }, } - _, _, err = cpm.Apply(context.Background(), repository.PackageResources{}) + _, _, err = cpm.apply(context.Background(), repository.PackageResources{}) if err == nil { t.Errorf("Expected error (unauthorized); got none") } cpm.credentialResolver = auth - r, _, err := cpm.Apply(context.Background(), repository.PackageResources{}) + r, _, err := cpm.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("task apply failed: %v", err) } diff --git a/pkg/task/edit.go b/pkg/task/edit.go index f6cd645d..59990c2b 100644 --- a/pkg/task/edit.go +++ b/pkg/task/edit.go @@ -34,7 +34,7 @@ type editPackageMutation struct { var _ mutation = &editPackageMutation{} -func (m *editPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *editPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "editPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/edit_test.go b/pkg/task/edit_test.go index 1c48d15d..0ddcf9b3 100644 --- a/pkg/task/edit_test.go +++ b/pkg/task/edit_test.go @@ -86,7 +86,7 @@ info: repoOpener: repoOpener, } - res, _, err := epm.Apply(context.Background(), repository.PackageResources{}) + res, _, err := epm.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("task apply failed: %v", err) } diff --git a/pkg/task/eval.go b/pkg/task/eval.go index 3bbf68b3..a0e79db6 100644 --- a/pkg/task/eval.go +++ b/pkg/task/eval.go @@ -35,7 +35,7 @@ type evalFunctionMutation struct { task *api.Task } -func (m *evalFunctionMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *evalFunctionMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "evalFunctionMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/generictaskhandler.go b/pkg/task/generictaskhandler.go new file mode 100644 index 00000000..abb2cdd4 --- /dev/null +++ b/pkg/task/generictaskhandler.go @@ -0,0 +1,694 @@ +// Copyright 2022,2024 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package task + +import ( + "bytes" + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/internal/kpt/fnruntime" + "github.com/nephio-project/porch/pkg/kpt" + kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/kpt/fn" + "github.com/nephio-project/porch/pkg/repository" + "go.opentelemetry.io/otel/trace" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/kustomize/kyaml/comments" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var _ TaskHandler = &genericTaskHandler{} + +type genericTaskHandler struct { + runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions + runtime fn.FunctionRuntime + repoOpener repository.RepositoryOpener + credentialResolver repository.CredentialResolver + referenceResolver repository.ReferenceResolver +} + +func (th *genericTaskHandler) GetRuntime() fn.FunctionRuntime { + return th.runtime +} + +func (th *genericTaskHandler) SetRunnerOptionsResolver(runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions) { + th.runnerOptionsResolver = runnerOptionsResolver +} + +func (th *genericTaskHandler) SetRuntime(runtime fn.FunctionRuntime) { + th.runtime = runtime +} + +func (th *genericTaskHandler) SetRepoOpener(repoOpener repository.RepositoryOpener) { + th.repoOpener = repoOpener +} + +func (th *genericTaskHandler) SetCredentialResolver(credentialResolver repository.CredentialResolver) { + th.credentialResolver = credentialResolver +} + +func (th *genericTaskHandler) SetReferenceResolver(referenceResolver repository.ReferenceResolver) { + th.referenceResolver = referenceResolver +} + +func (th *genericTaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { + var mutations []mutation + + // Unless first task is Init or Clone, insert Init to create an empty package. + tasks := obj.Spec.Tasks + if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { + mutations = append(mutations, &initPackageMutation{ + name: obj.Spec.PackageName, + task: &api.Task{ + Init: &api.PackageInitTaskSpec{ + Subpackage: "", + Description: fmt.Sprintf("%s description", obj.Spec.PackageName), + }, + }, + }) + } + + for i := range tasks { + task := &tasks[i] + mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) + if err != nil { + return err + } + mutations = append(mutations, mutation) + } + + // Render package after creation. + mutations = th.conditionalAddRender(obj, mutations) + + baseResources := repository.PackageResources{} + if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { + return err + } + + return nil +} + +func (th *genericTaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { + ctx, span := tracer.Start(ctx, "genericTaskHandler::DoPRMutations", trace.WithAttributes()) + defer span.End() + + var mutations []mutation + if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { + return fmt.Errorf("removing tasks is not yet supported") + } + for i := range oldObj.Spec.Tasks { + oldTask := &oldObj.Spec.Tasks[i] + newTask := &newObj.Spec.Tasks[i] + if oldTask.Type != newTask.Type { + return fmt.Errorf("changing task types is not yet supported") + } + } + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { + return fmt.Errorf("can only append one task at a time") + } + + newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] + if newTask.Type != api.TaskTypeUpdate { + return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) + } + if newTask.Update == nil { + return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) + } + + cloneTask := findCloneTask(oldObj) + if cloneTask == nil { + return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) + } + + mutation := &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: &newTask, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + namespace: namespace, + pkgName: oldObj.GetName(), + } + mutations = append(mutations, mutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // If any of the fields in the API that are projections from the Kptfile + // must be updated in the Kptfile as well. + kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) + if err != nil { + return err + } + if created { + kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) + if err != nil { + return err + } + mutations = append(mutations, kfPatchMutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // TODO: Handle the case if alongside lifecycle change, tasks are changed too. + // Update package contents only if the package is in draft state + if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { + apiResources, err := repoPR.GetResources(ctx) + if err != nil { + return fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: apiResources.Spec.Resources, + } + + if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { + return err + } + } + + return nil +} + +func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { + for _, tt := range oneOf { + if taskType == tt { + return true + } + } + return false +} + +func (th *genericTaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { + ctx, span := tracer.Start(ctx, "genericTaskHandler::DoPRResourceMutations", trace.WithAttributes()) + defer span.End() + + runnerOptions := th.runnerOptionsResolver(oldRes.GetNamespace()) + + mutations := []mutation{ + &mutationReplaceResources{ + newResources: newRes, + oldResources: oldRes, + }, + } + prevResources, err := pr2Update.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: prevResources.Spec.Resources, + } + + appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) + if err != nil { + return nil, err + } + + var renderStatus *api.RenderStatus + if len(appliedResources.Contents) > 0 { + // render the package + // Render failure will not fail the overall API operation. + // The render error and result is captured as part of renderStatus above + // and is returned in packageresourceresources API's status field. We continue with + // saving the non-rendered resources to avoid losing user's changes. + // and supress this err. + _, renderStatus, _ = applyResourceMutations(ctx, + draft, + appliedResources, + []mutation{&renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }}) + } else { + renderStatus = nil + } + + return renderStatus, nil +} + +func (th *genericTaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { + switch task.Type { + case api.TaskTypeInit: + if task.Init == nil { + return nil, fmt.Errorf("init not set for task of type %q", task.Type) + } + return &initPackageMutation{ + name: obj.Spec.PackageName, + task: task, + }, nil + case api.TaskTypeClone: + if task.Clone == nil { + return nil, fmt.Errorf("clone not set for task of type %q", task.Type) + } + return &clonePackageMutation{ + task: task, + namespace: obj.Namespace, + name: obj.Spec.PackageName, + isDeployment: isDeployment, + repoOpener: th.repoOpener, + credentialResolver: th.credentialResolver, + referenceResolver: th.referenceResolver, + packageConfig: packageConfig, + }, nil + + case api.TaskTypeUpdate: + if task.Update == nil { + return nil, fmt.Errorf("update not set for task of type %q", task.Type) + } + cloneTask := findCloneTask(obj) + if cloneTask == nil { + return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) + } + return &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: task, + namespace: obj.Namespace, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + pkgName: obj.Spec.PackageName, + }, nil + + case api.TaskTypePatch: + return buildPatchMutation(ctx, task) + + case api.TaskTypeEdit: + if task.Edit == nil { + return nil, fmt.Errorf("edit not set for task of type %q", task.Type) + } + return &editPackageMutation{ + task: task, + namespace: obj.Namespace, + packageName: obj.Spec.PackageName, + repositoryName: obj.Spec.RepositoryName, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + }, nil + + case api.TaskTypeEval: + if task.Eval == nil { + return nil, fmt.Errorf("eval not set for task of type %q", task.Type) + } + // TODO: We should find a different way to do this. Probably a separate + // task for render. + if task.Eval.Image == "render" { + runnerOptions := th.runnerOptionsResolver(obj.Namespace) + return &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }, nil + } else { + runnerOptions := th.runnerOptionsResolver(obj.Namespace) + return &evalFunctionMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + task: task, + }, nil + } + + default: + return nil, fmt.Errorf("task of type %q not supported", task.Type) + } +} + +func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { + kf, err := oldPackage.GetKptfile(ctx) + if err != nil { + return nil, false, err + } + + var orgKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + orgKfString = buf.String() + } + + var readinessGates []kptfile.ReadinessGate + for _, rg := range newObj.Spec.ReadinessGates { + readinessGates = append(readinessGates, kptfile.ReadinessGate{ + ConditionType: rg.ConditionType, + }) + } + + var conditions []kptfile.Condition + for _, c := range newObj.Status.Conditions { + conditions = append(conditions, kptfile.Condition{ + Type: c.Type, + Status: convertStatusToKptfile(c.Status), + Reason: c.Reason, + Message: c.Message, + }) + } + + if kf.Info == nil && len(readinessGates) > 0 { + kf.Info = &kptfile.PackageInfo{} + } + if len(readinessGates) > 0 { + kf.Info.ReadinessGates = readinessGates + } + + if kf.Status == nil && len(conditions) > 0 { + kf.Status = &kptfile.Status{} + } + if len(conditions) > 0 { + kf.Status.Conditions = conditions + } + + var newKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + newKfString = buf.String() + } + patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) + if err != nil { + return nil, false, err + } + // If patch is empty, don't create a Task. + if patchSpec.Contents == "" { + return nil, false, nil + } + + return &api.Task{ + Type: api.TaskTypePatch, + Patch: &api.PackagePatchTaskSpec{ + Patches: []api.PatchSpec{ + patchSpec, + }, + }, + }, true, nil +} + +func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { + switch s { + case api.ConditionTrue: + return kptfile.ConditionTrue + case api.ConditionFalse: + return kptfile.ConditionFalse + case api.ConditionUnknown: + return kptfile.ConditionUnknown + default: + panic(fmt.Errorf("unknown condition status: %v", s)) + } +} + +// conditionalAddRender adds a render mutation to the end of the mutations slice if the last +// entry is not already a render mutation. +func (th *genericTaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { + if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { + return mutations + } + + runnerOptions := th.runnerOptionsResolver(subject.GetNamespace()) + + return append(mutations, &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }) +} + +func isRenderMutation(m mutation) bool { + _, isRender := m.(*renderPackageMutation) + return isRender +} + +// applyResourceMutations mutates the resources and returns the most recent renderResult. +func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { + var lastApplied mutation + for _, m := range mutations { + updatedResources, taskResult, err := m.apply(ctx, baseResources) + if taskResult == nil && err == nil { + // a nil taskResult means nothing changed + continue + } + + var task *api.Task + if taskResult != nil { + task = taskResult.Task + } + if taskResult != nil && task.Type == api.TaskTypeEval { + renderStatus = taskResult.RenderStatus + } + if err != nil { + return updatedResources, renderStatus, err + } + + // if the last applied mutation was a render mutation, and so is this one, skip it + if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { + continue + } + lastApplied = m + + if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ + Spec: api.PackageRevisionResourcesSpec{ + Resources: updatedResources.Contents, + }, + }, task); err != nil { + return updatedResources, renderStatus, err + } + baseResources = updatedResources + applied = updatedResources + } + + return applied, renderStatus, nil +} + +type updatePackageMutation struct { + cloneTask *api.Task + updateTask *api.Task + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver + namespace string + pkgName string +} + +func (m *updatePackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) + defer span.End() + + currUpstreamPkgRef, err := m.currUpstream() + if err != nil { + return repository.PackageResources{}, nil, err + } + + targetUpstream := m.updateTask.Update.Upstream + if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { + return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") + } + + originalResources, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", + m.pkgName, *currUpstreamPkgRef) + } + + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) + } + upstreamResources, err := upstreamRevision.GetResources(ctx) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) + } + + klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", + m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) + + // May be have packageUpdater part of the Porch core to make it easy for testing ? + updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, + resources, + repository.PackageResources{ + Contents: originalResources.Spec.Resources, + }, + repository.PackageResources{ + Contents: upstreamResources.Spec.Resources, + }) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) + } + + newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) + } + if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) + } + + // ensure merge-key comment is added to newly added resources. + result, err := ensureMergeKey(ctx, updatedResources) + if err != nil { + klog.Infof("failed to add merge key comments: %v", err) + } + return result, &api.TaskResult{Task: m.updateTask}, nil +} + +// Currently assumption is that downstream packages will be forked from a porch package. +// As per current implementation, upstream package ref is stored in a new update task but this may +// change so the logic of figuring out current upstream will live in this function. +func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { + if m.cloneTask == nil || m.cloneTask.Clone == nil { + return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) + } + upstream := m.cloneTask.Clone.Upstream + if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { + return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) + } + return upstream.UpstreamRef, nil +} + +func findCloneTask(pr *api.PackageRevision) *api.Task { + if len(pr.Spec.Tasks) == 0 { + return nil + } + firstTask := pr.Spec.Tasks[0] + if firstTask.Type == api.TaskTypeClone { + return &firstTask + } + return nil +} + +type mutationReplaceResources struct { + newResources *api.PackageRevisionResources + oldResources *api.PackageRevisionResources +} + +func (m *mutationReplaceResources) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) + defer span.End() + + patch := &api.PackagePatchTaskSpec{} + + old := resources.Contents + new, err := healConfig(old, m.newResources.Spec.Resources) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) + } + + for k, newV := range new { + oldV, ok := old[k] + // New config or changed config + if !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeCreateFile, + Contents: newV, + } + patch.Patches = append(patch.Patches, patchSpec) + } else if newV != oldV { + patchSpec, err := GeneratePatch(k, oldV, newV) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) + } + if patchSpec.Contents == "" { + continue + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + for k := range old { + // Deleted config + if _, ok := new[k]; !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeDeleteFile, + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + // If patch is empty, don't create a Task. + var taskResult *api.TaskResult + if len(patch.Patches) > 0 { + taskResult = &api.TaskResult{ + Task: &api.Task{ + Type: api.TaskTypePatch, + Patch: patch, + }, + } + } + return repository.PackageResources{Contents: new}, taskResult, nil +} + +func healConfig(old, new map[string]string) (map[string]string, error) { + // Copy comments from old config to new + oldResources, err := (&packageReader{ + input: repository.PackageResources{Contents: old}, + extra: map[string]string{}, + }).Read() + if err != nil { + return nil, fmt.Errorf("failed to read old packge resources: %w", err) + } + + var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { + for _, n := range r { + for _, original := range oldResources { + if n.GetNamespace() == original.GetNamespace() && + n.GetName() == original.GetName() && + n.GetApiVersion() == original.GetApiVersion() && + n.GetKind() == original.GetKind() { + comments.CopyComments(original, n) + } + } + } + return r, nil + } + + out := &packageWriter{ + output: repository.PackageResources{ + Contents: map[string]string{}, + }, + } + + extra := map[string]string{} + + if err := (kio.Pipeline{ + Inputs: []kio.Reader{&packageReader{ + input: repository.PackageResources{Contents: new}, + extra: extra, + }}, + Filters: []kio.Filter{filter}, + Outputs: []kio.Writer{out}, + ContinueOnEmptyResult: true, + }).Execute(); err != nil { + return nil, err + } + + healed := out.output.Contents + + for k, v := range extra { + healed[k] = v + } + + return healed, nil +} diff --git a/pkg/task/init.go b/pkg/task/init.go index a279f732..5e113499 100644 --- a/pkg/task/init.go +++ b/pkg/task/init.go @@ -35,7 +35,7 @@ type initPackageMutation struct { var _ mutation = &initPackageMutation{} -func (m *initPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *initPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "initPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/init_test.go b/pkg/task/init_test.go index 0593f1aa..93c0c634 100644 --- a/pkg/task/init_test.go +++ b/pkg/task/init_test.go @@ -42,7 +42,7 @@ func TestInit(t *testing.T) { t.Fatalf("Failed to find testdata: %v", err) } - initializedPkg, _, err := init.Apply(context.Background(), repository.PackageResources{}) + initializedPkg, _, err := init.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("package init failed: %v", err) } diff --git a/pkg/task/patchgen.go b/pkg/task/patchgen.go index e27b14ed..6cb5b73a 100644 --- a/pkg/task/patchgen.go +++ b/pkg/task/patchgen.go @@ -52,7 +52,7 @@ type applyPatchMutation struct { var _ mutation = &applyPatchMutation{} -func (m *applyPatchMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *applyPatchMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { _, span := tracer.Start(ctx, "applyPatchMutation:::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/render.go b/pkg/task/render.go index 31d5744b..dc91df2c 100644 --- a/pkg/task/render.go +++ b/pkg/task/render.go @@ -39,7 +39,7 @@ type renderPackageMutation struct { var _ mutation = &renderPackageMutation{} -func (m *renderPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *renderPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "renderPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/render_test.go b/pkg/task/render_test.go index bf24b6b1..c2a16268 100644 --- a/pkg/task/render_test.go +++ b/pkg/task/render_test.go @@ -60,7 +60,7 @@ func TestRender(t *testing.T) { t.Fatalf("Failed to read package: %v", err) } - rendered, _, err := render.Apply(context.Background(), w.output) + rendered, _, err := render.apply(context.Background(), w.output) if err != nil { t.Errorf("package render failed: %v", err) } diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index fc29a4cc..571f361d 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -46,7 +46,7 @@ func TestReplaceResources(t *testing.T) { }, } - output, _, err := replace.Apply(ctx, input) + output, _, err := replace.apply(ctx, input) if err != nil { t.Fatalf("mutationReplaceResources.Apply failed: %v", err) } diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go index 5ea48726..030e5bef 100644 --- a/pkg/task/taskhandler.go +++ b/pkg/task/taskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2022,2024 The kpt and Nephio Authors +// Copyright 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,661 +15,37 @@ package task import ( - "bytes" "context" - "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/internal/kpt/builtins" "github.com/nephio-project/porch/internal/kpt/fnruntime" - "github.com/nephio-project/porch/pkg/kpt" - kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/kpt/fn" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" - "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/kustomize/kyaml/comments" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" ) var tracer = otel.Tracer("task") -type TaskHandler struct { - RunnerOptionsResolver func(namespace string) fnruntime.RunnerOptions - Runtime fn.FunctionRuntime - RepoOpener repository.RepositoryOpener - CredentialResolver repository.CredentialResolver - ReferenceResolver repository.ReferenceResolver -} - -type mutation interface { - Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) -} - -func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { - for _, tt := range oneOf { - if taskType == tt { - return true - } - } - return false -} - -func (th *TaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { - var mutations []mutation - - // Unless first task is Init or Clone, insert Init to create an empty package. - tasks := obj.Spec.Tasks - if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { - mutations = append(mutations, &initPackageMutation{ - name: obj.Spec.PackageName, - task: &api.Task{ - Init: &api.PackageInitTaskSpec{ - Subpackage: "", - Description: fmt.Sprintf("%s description", obj.Spec.PackageName), - }, - }, - }) - } - - for i := range tasks { - task := &tasks[i] - mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) - if err != nil { - return err - } - mutations = append(mutations, mutation) - } - - // Render package after creation. - mutations = th.conditionalAddRender(obj, mutations) - - baseResources := repository.PackageResources{} - if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { - return err - } - - return nil -} - -func (th *TaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { - switch task.Type { - case api.TaskTypeInit: - if task.Init == nil { - return nil, fmt.Errorf("init not set for task of type %q", task.Type) - } - return &initPackageMutation{ - name: obj.Spec.PackageName, - task: task, - }, nil - case api.TaskTypeClone: - if task.Clone == nil { - return nil, fmt.Errorf("clone not set for task of type %q", task.Type) - } - return &clonePackageMutation{ - task: task, - namespace: obj.Namespace, - name: obj.Spec.PackageName, - isDeployment: isDeployment, - repoOpener: th.RepoOpener, - credentialResolver: th.CredentialResolver, - referenceResolver: th.ReferenceResolver, - packageConfig: packageConfig, - }, nil - - case api.TaskTypeUpdate: - if task.Update == nil { - return nil, fmt.Errorf("update not set for task of type %q", task.Type) - } - cloneTask := findCloneTask(obj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) - } - return &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: task, - namespace: obj.Namespace, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - pkgName: obj.Spec.PackageName, - }, nil - - case api.TaskTypePatch: - return buildPatchMutation(ctx, task) - - case api.TaskTypeEdit: - if task.Edit == nil { - return nil, fmt.Errorf("edit not set for task of type %q", task.Type) - } - return &editPackageMutation{ - task: task, - namespace: obj.Namespace, - packageName: obj.Spec.PackageName, - repositoryName: obj.Spec.RepositoryName, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - }, nil - - case api.TaskTypeEval: - if task.Eval == nil { - return nil, fmt.Errorf("eval not set for task of type %q", task.Type) - } - // TODO: We should find a different way to do this. Probably a separate - // task for render. - if task.Eval.Image == "render" { - runnerOptions := th.RunnerOptionsResolver(obj.Namespace) - return &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }, nil - } else { - runnerOptions := th.RunnerOptionsResolver(obj.Namespace) - return &evalFunctionMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - task: task, - }, nil - } - - default: - return nil, fmt.Errorf("task of type %q not supported", task.Type) - } -} - -func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { - kf, err := oldPackage.GetKptfile(ctx) - if err != nil { - return nil, false, err - } - - var orgKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - orgKfString = buf.String() - } - - var readinessGates []kptfile.ReadinessGate - for _, rg := range newObj.Spec.ReadinessGates { - readinessGates = append(readinessGates, kptfile.ReadinessGate{ - ConditionType: rg.ConditionType, - }) - } - - var conditions []kptfile.Condition - for _, c := range newObj.Status.Conditions { - conditions = append(conditions, kptfile.Condition{ - Type: c.Type, - Status: convertStatusToKptfile(c.Status), - Reason: c.Reason, - Message: c.Message, - }) - } - - if kf.Info == nil && len(readinessGates) > 0 { - kf.Info = &kptfile.PackageInfo{} - } - if len(readinessGates) > 0 { - kf.Info.ReadinessGates = readinessGates - } - - if kf.Status == nil && len(conditions) > 0 { - kf.Status = &kptfile.Status{} - } - if len(conditions) > 0 { - kf.Status.Conditions = conditions - } - - var newKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - newKfString = buf.String() - } - patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) - if err != nil { - return nil, false, err - } - // If patch is empty, don't create a Task. - if patchSpec.Contents == "" { - return nil, false, nil - } - - return &api.Task{ - Type: api.TaskTypePatch, - Patch: &api.PackagePatchTaskSpec{ - Patches: []api.PatchSpec{ - patchSpec, - }, - }, - }, true, nil -} - -func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { - switch s { - case api.ConditionTrue: - return kptfile.ConditionTrue - case api.ConditionFalse: - return kptfile.ConditionFalse - case api.ConditionUnknown: - return kptfile.ConditionUnknown - default: - panic(fmt.Errorf("unknown condition status: %v", s)) - } -} - -// conditionalAddRender adds a render mutation to the end of the mutations slice if the last -// entry is not already a render mutation. -func (th *TaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { - if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { - return mutations - } - - runnerOptions := th.RunnerOptionsResolver(subject.GetNamespace()) - - return append(mutations, &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }) -} - -func isRenderMutation(m mutation) bool { - _, isRender := m.(*renderPackageMutation) - return isRender -} - -// applyResourceMutations mutates the resources and returns the most recent renderResult. -func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { - var lastApplied mutation - for _, m := range mutations { - updatedResources, taskResult, err := m.Apply(ctx, baseResources) - if taskResult == nil && err == nil { - // a nil taskResult means nothing changed - continue - } - - var task *api.Task - if taskResult != nil { - task = taskResult.Task - } - if taskResult != nil && task.Type == api.TaskTypeEval { - renderStatus = taskResult.RenderStatus - } - if err != nil { - return updatedResources, renderStatus, err - } - - // if the last applied mutation was a render mutation, and so is this one, skip it - if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { - continue - } - lastApplied = m - - if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ - Spec: api.PackageRevisionResourcesSpec{ - Resources: updatedResources.Contents, - }, - }, task); err != nil { - return updatedResources, renderStatus, err - } - baseResources = updatedResources - applied = updatedResources - } - - return applied, renderStatus, nil -} - -type updatePackageMutation struct { - cloneTask *api.Task - updateTask *api.Task - repoOpener repository.RepositoryOpener - referenceResolver repository.ReferenceResolver - namespace string - pkgName string -} - -func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) - defer span.End() - - currUpstreamPkgRef, err := m.currUpstream() - if err != nil { - return repository.PackageResources{}, nil, err - } - - targetUpstream := m.updateTask.Update.Upstream - if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { - return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") - } - - originalResources, err := (&repository.PackageFetcher{ - RepoOpener: m.repoOpener, - ReferenceResolver: m.referenceResolver, - }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", - m.pkgName, *currUpstreamPkgRef) - } - - upstreamRevision, err := (&repository.PackageFetcher{ - RepoOpener: m.repoOpener, - ReferenceResolver: m.referenceResolver, - }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) - } - upstreamResources, err := upstreamRevision.GetResources(ctx) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) - } - - klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", - m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) - - // May be have packageUpdater part of the Porch core to make it easy for testing ? - updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, - resources, - repository.PackageResources{ - Contents: originalResources.Spec.Resources, - }, - repository.PackageResources{ - Contents: upstreamResources.Spec.Resources, - }) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) - } - - newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) - } - if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) - } - - // ensure merge-key comment is added to newly added resources. - result, err := ensureMergeKey(ctx, updatedResources) - if err != nil { - klog.Infof("failed to add merge key comments: %v", err) - } - return result, &api.TaskResult{Task: m.updateTask}, nil -} - -// Currently assumption is that downstream packages will be forked from a porch package. -// As per current implementation, upstream package ref is stored in a new update task but this may -// change so the logic of figuring out current upstream will live in this function. -func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { - if m.cloneTask == nil || m.cloneTask.Clone == nil { - return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) - } - upstream := m.cloneTask.Clone.Upstream - if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { - return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) - } - return upstream.UpstreamRef, nil -} - -func findCloneTask(pr *api.PackageRevision) *api.Task { - if len(pr.Spec.Tasks) == 0 { - return nil - } - firstTask := pr.Spec.Tasks[0] - if firstTask.Type == api.TaskTypeClone { - return &firstTask - } - return nil -} - -type mutationReplaceResources struct { - newResources *api.PackageRevisionResources - oldResources *api.PackageRevisionResources -} - -func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) - defer span.End() - - patch := &api.PackagePatchTaskSpec{} - - old := resources.Contents - new, err := healConfig(old, m.newResources.Spec.Resources) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) - } - - for k, newV := range new { - oldV, ok := old[k] - // New config or changed config - if !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeCreateFile, - Contents: newV, - } - patch.Patches = append(patch.Patches, patchSpec) - } else if newV != oldV { - patchSpec, err := GeneratePatch(k, oldV, newV) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) - } - if patchSpec.Contents == "" { - continue - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - for k := range old { - // Deleted config - if _, ok := new[k]; !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeDeleteFile, - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - // If patch is empty, don't create a Task. - var taskResult *api.TaskResult - if len(patch.Patches) > 0 { - taskResult = &api.TaskResult{ - Task: &api.Task{ - Type: api.TaskTypePatch, - Patch: patch, - }, - } - } - return repository.PackageResources{Contents: new}, taskResult, nil -} +type TaskHandler interface { + GetRuntime() fn.FunctionRuntime -func healConfig(old, new map[string]string) (map[string]string, error) { - // Copy comments from old config to new - oldResources, err := (&packageReader{ - input: repository.PackageResources{Contents: old}, - extra: map[string]string{}, - }).Read() - if err != nil { - return nil, fmt.Errorf("failed to read old packge resources: %w", err) - } + SetRunnerOptionsResolver(func(namespace string) fnruntime.RunnerOptions) + SetRuntime(fn.FunctionRuntime) + SetRepoOpener(repository.RepositoryOpener) + SetCredentialResolver(repository.CredentialResolver) + SetReferenceResolver(repository.ReferenceResolver) - var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { - for _, n := range r { - for _, original := range oldResources { - if n.GetNamespace() == original.GetNamespace() && - n.GetName() == original.GetName() && - n.GetApiVersion() == original.GetApiVersion() && - n.GetKind() == original.GetKind() { - comments.CopyComments(original, n) - } - } - } - return r, nil - } - - out := &packageWriter{ - output: repository.PackageResources{ - Contents: map[string]string{}, - }, - } - - extra := map[string]string{} - - if err := (kio.Pipeline{ - Inputs: []kio.Reader{&packageReader{ - input: repository.PackageResources{Contents: new}, - extra: extra, - }}, - Filters: []kio.Filter{filter}, - Outputs: []kio.Writer{out}, - ContinueOnEmptyResult: true, - }).Execute(); err != nil { - return nil, err - } - - healed := out.output.Contents - - for k, v := range extra { - healed[k] = v - } - - return healed, nil + ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error + DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error + DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) } -func (th *TaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { - ctx, span := tracer.Start(ctx, "TaskHandler::DoPRMutations", trace.WithAttributes()) - defer span.End() - - var mutations []mutation - if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { - return fmt.Errorf("removing tasks is not yet supported") - } - for i := range oldObj.Spec.Tasks { - oldTask := &oldObj.Spec.Tasks[i] - newTask := &newObj.Spec.Tasks[i] - if oldTask.Type != newTask.Type { - return fmt.Errorf("changing task types is not yet supported") - } - } - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { - return fmt.Errorf("can only append one task at a time") - } - - newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] - if newTask.Type != api.TaskTypeUpdate { - return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) - } - if newTask.Update == nil { - return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) - } - - cloneTask := findCloneTask(oldObj) - if cloneTask == nil { - return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) - } - - mutation := &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: &newTask, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - namespace: namespace, - pkgName: oldObj.GetName(), - } - mutations = append(mutations, mutation) - } - - // Re-render if we are making changes. - mutations = th.conditionalAddRender(newObj, mutations) - - // If any of the fields in the API that are projections from the Kptfile - // must be updated in the Kptfile as well. - kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) - if err != nil { - return err - } - if created { - kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) - if err != nil { - return err - } - mutations = append(mutations, kfPatchMutation) - } - - // Re-render if we are making changes. - mutations = th.conditionalAddRender(newObj, mutations) - - // TODO: Handle the case if alongside lifecycle change, tasks are changed too. - // Update package contents only if the package is in draft state - if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { - apiResources, err := repoPR.GetResources(ctx) - if err != nil { - return fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: apiResources.Spec.Resources, - } - - if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { - return err - } - } - - return nil +type mutation interface { + apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) } -func (th *TaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { - ctx, span := tracer.Start(ctx, "TaskHandler::DoPRResourceMutations", trace.WithAttributes()) - defer span.End() - - runnerOptions := th.RunnerOptionsResolver(oldRes.GetNamespace()) - - mutations := []mutation{ - &mutationReplaceResources{ - newResources: newRes, - oldResources: oldRes, - }, - } - prevResources, err := pr2Update.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: prevResources.Spec.Resources, - } - - appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) - if err != nil { - return nil, err - } - - var renderStatus *api.RenderStatus - if len(appliedResources.Contents) > 0 { - // render the package - // Render failure will not fail the overall API operation. - // The render error and result is captured as part of renderStatus above - // and is returned in packageresourceresources API's status field. We continue with - // saving the non-rendered resources to avoid losing user's changes. - // and supress this err. - _, renderStatus, _ = applyResourceMutations(ctx, - draft, - appliedResources, - []mutation{&renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }}) - } else { - renderStatus = nil - } - - return renderStatus, nil +func GetDefaultTaskHandler() TaskHandler { + return &genericTaskHandler{} } From c77eb6766078f28a752dc6bca867f02798749870 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:39:33 +0000 Subject: [PATCH 05/13] Update license dates on copyright notices --- pkg/cache/cache.go | 14 ++++++++++++++ pkg/cache/fake/objectnotifier.go | 2 +- pkg/cache/memory/cache.go | 2 +- pkg/cache/memory/cache_test.go | 2 +- pkg/cache/memory/draft.go | 2 +- pkg/cache/memory/package.go | 2 +- pkg/cache/memory/packagerevision.go | 2 +- pkg/cache/memory/util.go | 2 +- pkg/engine/options.go | 2 +- pkg/engine/watchermanager.go | 2 +- pkg/git/git.go | 2 +- pkg/git/package.go | 2 +- pkg/oci/oci.go | 2 +- pkg/registry/porch/package.go | 2 +- pkg/registry/porch/packagecommon.go | 2 +- pkg/registry/porch/packagerevision.go | 2 +- pkg/registry/porch/packagerevisionresources.go | 2 +- pkg/registry/porch/reference.go | 2 +- pkg/registry/porch/watch.go | 2 +- pkg/registry/porch/watch_test.go | 2 +- pkg/repository/environment.go | 2 +- pkg/repository/fake/packagerevision.go | 2 +- pkg/repository/fake/repository.go | 2 +- pkg/repository/package.go | 2 +- pkg/repository/repository.go | 2 +- pkg/repository/testing.go | 2 +- pkg/repository/update.go | 2 +- pkg/repository/update_test.go | 2 +- pkg/task/builtin.go | 2 +- pkg/task/builtin_test.go | 2 +- pkg/task/clone.go | 2 +- pkg/task/clone_test.go | 2 +- pkg/task/edit.go | 2 +- pkg/task/edit_test.go | 2 +- pkg/task/eval.go | 2 +- pkg/task/generictaskhandler.go | 2 +- pkg/task/init.go | 2 +- pkg/task/init_test.go | 2 +- pkg/task/kio.go | 2 +- pkg/task/mergekey.go | 2 +- pkg/task/patch_test.go | 2 +- pkg/task/patchgen.go | 2 +- pkg/task/patchgen_test.go | 2 +- pkg/task/render.go | 2 +- pkg/task/render_test.go | 2 +- pkg/task/replace_test.go | 2 +- pkg/task/taskhandler.go | 2 +- pkg/util/util.go | 2 +- 48 files changed, 61 insertions(+), 47 deletions(-) diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 6b9918f5..1d411aea 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cache import ( diff --git a/pkg/cache/fake/objectnotifier.go b/pkg/cache/fake/objectnotifier.go index f9bde5aa..a8cdf94e 100644 --- a/pkg/cache/fake/objectnotifier.go +++ b/pkg/cache/fake/objectnotifier.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022. 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index 3cd53330..45984387 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/cache_test.go b/pkg/cache/memory/cache_test.go index de2fbc97..4f2323ae 100644 --- a/pkg/cache/memory/cache_test.go +++ b/pkg/cache/memory/cache_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/draft.go b/pkg/cache/memory/draft.go index 5e44b259..0e21b833 100644 --- a/pkg/cache/memory/draft.go +++ b/pkg/cache/memory/draft.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/package.go b/pkg/cache/memory/package.go index f8528bcc..d02093f2 100644 --- a/pkg/cache/memory/package.go +++ b/pkg/cache/memory/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index 0aed7cba..f12f8d62 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index cdb3ca1c..c69d402b 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/engine/options.go b/pkg/engine/options.go index 8e84f16a..fa0422ab 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/engine/watchermanager.go b/pkg/engine/watchermanager.go index b64c30be..768e68d3 100644 --- a/pkg/engine/watchermanager.go +++ b/pkg/engine/watchermanager.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/git/git.go b/pkg/git/git.go index bca58d2a..33b6b6a6 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/git/package.go b/pkg/git/package.go index b3c2ff26..be203b4d 100644 --- a/pkg/git/package.go +++ b/pkg/git/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index 4b683ac8..0983dbc5 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/package.go b/pkg/registry/porch/package.go index e5d8414f..e42dd1a8 100644 --- a/pkg/registry/porch/package.go +++ b/pkg/registry/porch/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagecommon.go b/pkg/registry/porch/packagecommon.go index 74ddb44b..78400c2d 100644 --- a/pkg/registry/porch/packagecommon.go +++ b/pkg/registry/porch/packagecommon.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagerevision.go b/pkg/registry/porch/packagerevision.go index 357d1ae7..64d0ff77 100644 --- a/pkg/registry/porch/packagerevision.go +++ b/pkg/registry/porch/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagerevisionresources.go b/pkg/registry/porch/packagerevisionresources.go index bd6fc6fc..10388484 100644 --- a/pkg/registry/porch/packagerevisionresources.go +++ b/pkg/registry/porch/packagerevisionresources.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/reference.go b/pkg/registry/porch/reference.go index 5fce1ab9..1026ca40 100644 --- a/pkg/registry/porch/reference.go +++ b/pkg/registry/porch/reference.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index 4f5de58f..38102610 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/watch_test.go b/pkg/registry/porch/watch_test.go index bbf91d91..fa21fa95 100644 --- a/pkg/registry/porch/watch_test.go +++ b/pkg/registry/porch/watch_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/environment.go b/pkg/repository/environment.go index 96d542dc..7fe8e7c9 100644 --- a/pkg/repository/environment.go +++ b/pkg/repository/environment.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go index 187b6f0b..8f962065 100644 --- a/pkg/repository/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/fake/repository.go b/pkg/repository/fake/repository.go index ff9abd72..9fd6c0c8 100644 --- a/pkg/repository/fake/repository.go +++ b/pkg/repository/fake/repository.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/package.go b/pkg/repository/package.go index 77836afd..e8383a6d 100644 --- a/pkg/repository/package.go +++ b/pkg/repository/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 91f620de..1aa2fa3f 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/testing.go b/pkg/repository/testing.go index 6a37f03f..54af916f 100644 --- a/pkg/repository/testing.go +++ b/pkg/repository/testing.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/update.go b/pkg/repository/update.go index 0843e2f6..64d3290d 100644 --- a/pkg/repository/update.go +++ b/pkg/repository/update.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/update_test.go b/pkg/repository/update_test.go index e10a25d1..e18e05c9 100644 --- a/pkg/repository/update_test.go +++ b/pkg/repository/update_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/builtin.go b/pkg/task/builtin.go index d594309e..e6d56f3a 100644 --- a/pkg/task/builtin.go +++ b/pkg/task/builtin.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/builtin_test.go b/pkg/task/builtin_test.go index f6725ac1..c84eac43 100644 --- a/pkg/task/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/clone.go b/pkg/task/clone.go index 17928f5a..f5e2ee06 100644 --- a/pkg/task/clone.go +++ b/pkg/task/clone.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/clone_test.go b/pkg/task/clone_test.go index 3422b851..d08e3e3d 100644 --- a/pkg/task/clone_test.go +++ b/pkg/task/clone_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/edit.go b/pkg/task/edit.go index 59990c2b..01841465 100644 --- a/pkg/task/edit.go +++ b/pkg/task/edit.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/edit_test.go b/pkg/task/edit_test.go index 0ddcf9b3..bedeb4fd 100644 --- a/pkg/task/edit_test.go +++ b/pkg/task/edit_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/eval.go b/pkg/task/eval.go index a0e79db6..982322e3 100644 --- a/pkg/task/eval.go +++ b/pkg/task/eval.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/generictaskhandler.go b/pkg/task/generictaskhandler.go index abb2cdd4..e5ba8e51 100644 --- a/pkg/task/generictaskhandler.go +++ b/pkg/task/generictaskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2022,2024 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/init.go b/pkg/task/init.go index 5e113499..a5f7562b 100644 --- a/pkg/task/init.go +++ b/pkg/task/init.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/init_test.go b/pkg/task/init_test.go index 93c0c634..bb383f28 100644 --- a/pkg/task/init_test.go +++ b/pkg/task/init_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/kio.go b/pkg/task/kio.go index 6b253a72..390ab714 100644 --- a/pkg/task/kio.go +++ b/pkg/task/kio.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/mergekey.go b/pkg/task/mergekey.go index 93d97579..0765f842 100644 --- a/pkg/task/mergekey.go +++ b/pkg/task/mergekey.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patch_test.go b/pkg/task/patch_test.go index eb116388..32961e88 100644 --- a/pkg/task/patch_test.go +++ b/pkg/task/patch_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patchgen.go b/pkg/task/patchgen.go index 6cb5b73a..1046ba0d 100644 --- a/pkg/task/patchgen.go +++ b/pkg/task/patchgen.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patchgen_test.go b/pkg/task/patchgen_test.go index 57aaeacc..00448358 100644 --- a/pkg/task/patchgen_test.go +++ b/pkg/task/patchgen_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/render.go b/pkg/task/render.go index dc91df2c..56a3ae71 100644 --- a/pkg/task/render.go +++ b/pkg/task/render.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/render_test.go b/pkg/task/render_test.go index c2a16268..1d1fc17a 100644 --- a/pkg/task/render_test.go +++ b/pkg/task/render_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index 571f361d..062c8805 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go index 030e5bef..e30f3336 100644 --- a/pkg/task/taskhandler.go +++ b/pkg/task/taskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2024 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/util/util.go b/pkg/util/util.go index c323add7..164f9c25 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,4 +1,4 @@ -// Copyright 2023 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From fef6e7f0b0aeffdcbaa4a8406abf5a6cb05680cd Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:41:16 +0000 Subject: [PATCH 06/13] Update license dates on copyright notices --- pkg/util/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/util.go b/pkg/util/util.go index 164f9c25..c331b586 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,4 +1,4 @@ -// Copyright 2022, 2024 The kpt and Nephio Authors +// Copyright 2023, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 9d3ffa3c3cc196c9ed5a9dc84b3179c961e4e1ca Mon Sep 17 00:00:00 2001 From: Liam Fallon <35595825+liamfallon@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:26:04 +0000 Subject: [PATCH 07/13] Update pkg/cache/fake/objectnotifier.go --- pkg/cache/fake/objectnotifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cache/fake/objectnotifier.go b/pkg/cache/fake/objectnotifier.go index a8cdf94e..119c3979 100644 --- a/pkg/cache/fake/objectnotifier.go +++ b/pkg/cache/fake/objectnotifier.go @@ -1,4 +1,4 @@ -// Copyright 2022. 2024 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 6a45dc9a89967e4471fa14e83afcc044c4e19b75 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Tue, 12 Nov 2024 18:21:31 +0000 Subject: [PATCH 08/13] Initial cut at porch core refactor --- pkg/cache/cache.go | 20 +- pkg/cache/fake/objectnotifier.go | 3 +- pkg/cache/memory/cache.go | 4 +- pkg/cache/memory/cache_test.go | 3 +- pkg/cache/memory/draft.go | 2 - pkg/cache/memory/package.go | 2 - pkg/cache/memory/packagerevision.go | 53 +- pkg/cache/memory/repository.go | 38 +- pkg/cache/memory/util.go | 1 + pkg/engine/doc.go | 17 - pkg/engine/engine.go | 961 ++---------------- pkg/engine/options.go | 29 +- pkg/engine/package.go | 76 -- pkg/engine/testdata/clone/bucket/Kptfile | 20 - pkg/engine/testdata/clone/bucket/bucket.yaml | 26 - pkg/engine/testdata/clone/configmap/Kptfile | 20 - .../testdata/clone/configmap/configmap.yaml | 21 - pkg/engine/testdata/context/expected/Kptfile | 6 - .../testdata/context/expected/bucket.yaml | 12 - .../context/expected/package-context.yaml | 9 - pkg/engine/testdata/context/input/Kptfile | 6 - pkg/engine/testdata/context/input/bucket.yaml | 12 - pkg/engine/testdata/init/testpkg/Kptfile | 13 - pkg/engine/testdata/init/testpkg/README.md | 21 - .../init/testpkg/package-context.yaml | 8 - pkg/engine/testdata/replace/Kptfile | 23 - pkg/engine/testdata/replace/README.md | 1 - pkg/engine/testdata/replace/bucket.yaml | 18 - .../testdata/simple-render/expected.txt | 27 - .../simple-render/simple-bucket/Kptfile | 29 - .../simple-render/simple-bucket/bucket.yaml | 26 - pkg/engine/testdata/update/local/Kptfile | 27 - pkg/engine/testdata/update/local/README.md | 21 - .../testdata/update/local/namespace.yaml | 5 - .../update/local/package-context.yaml | 8 - .../testdata/update/local/resourcequota.yaml | 9 - .../testdata/update/local/rolebinding.yaml | 13 - .../update/local/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/original/Kptfile | 14 - pkg/engine/testdata/update/original/README.md | 21 - .../testdata/update/original/namespace.yaml | 5 - .../update/original/package-context.yaml | 8 - .../update/original/resourcequota.yaml | 9 - .../testdata/update/original/rolebinding.yaml | 13 - .../update/original/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/updated/Kptfile | 27 - pkg/engine/testdata/update/updated/README.md | 21 - .../testdata/update/updated/namespace.yaml | 5 - .../update/updated/package-context.yaml | 8 - .../update/updated/resourcequota.yaml | 9 - .../testdata/update/updated/rolebinding.yaml | 13 - .../update/updated/update-rolebinding.yaml | 20 - pkg/engine/testdata/update/upstream/Kptfile | 14 - pkg/engine/testdata/update/upstream/README.md | 21 - .../testdata/update/upstream/namespace.yaml | 5 - .../update/upstream/package-context.yaml | 8 - .../update/upstream/resourcequota.yaml | 9 - .../testdata/update/upstream/rolebinding.yaml | 13 - .../update/upstream/update-rolebinding.yaml | 20 - pkg/engine/watchermanager.go | 7 +- pkg/git/git.go | 6 +- pkg/git/package.go | 10 + pkg/oci/oci.go | 14 + pkg/registry/porch/package.go | 8 +- pkg/registry/porch/packagecommon.go | 19 +- pkg/registry/porch/packagerevision.go | 6 +- .../porch/packagerevisionresources.go | 7 +- pkg/registry/porch/reference.go | 8 +- pkg/registry/porch/watch.go | 15 +- pkg/registry/porch/watch_test.go | 10 +- pkg/{engine => repository}/environment.go | 7 +- .../fake/packagerevision.go | 45 +- pkg/{engine => repository}/fake/repository.go | 4 + pkg/repository/package.go | 153 +++ pkg/repository/repository.go | 7 + pkg/{engine => repository}/testing.go | 10 +- pkg/{engine => repository}/update.go | 33 +- pkg/{engine => repository}/update_test.go | 4 +- pkg/{engine => task}/builtin.go | 2 +- pkg/{engine => task}/builtin_test.go | 9 +- pkg/{engine => task}/clone.go | 21 +- pkg/{engine => task}/clone_test.go | 2 +- pkg/{engine => task}/edit.go | 12 +- pkg/{engine => task}/edit_test.go | 8 +- pkg/{engine => task}/eval.go | 2 +- pkg/{engine => task}/init.go | 2 +- pkg/{engine => task}/init_test.go | 2 +- pkg/{engine => task}/kio.go | 2 +- pkg/{engine => task}/mergekey.go | 2 +- .../engine_test.go => task/patch_test.go} | 14 +- pkg/{engine => task}/patchgen.go | 2 +- pkg/{engine => task}/patchgen_test.go | 2 +- pkg/{engine => task}/render.go | 2 +- pkg/{engine => task}/render_test.go | 2 +- pkg/{engine => task}/replace_test.go | 4 +- pkg/task/taskhandler.go | 675 ++++++++++++ pkg/util/util.go | 9 + 97 files changed, 1190 insertions(+), 1850 deletions(-) delete mode 100644 pkg/engine/doc.go delete mode 100644 pkg/engine/package.go delete mode 100644 pkg/engine/testdata/clone/bucket/Kptfile delete mode 100644 pkg/engine/testdata/clone/bucket/bucket.yaml delete mode 100644 pkg/engine/testdata/clone/configmap/Kptfile delete mode 100644 pkg/engine/testdata/clone/configmap/configmap.yaml delete mode 100644 pkg/engine/testdata/context/expected/Kptfile delete mode 100644 pkg/engine/testdata/context/expected/bucket.yaml delete mode 100644 pkg/engine/testdata/context/expected/package-context.yaml delete mode 100644 pkg/engine/testdata/context/input/Kptfile delete mode 100644 pkg/engine/testdata/context/input/bucket.yaml delete mode 100644 pkg/engine/testdata/init/testpkg/Kptfile delete mode 100644 pkg/engine/testdata/init/testpkg/README.md delete mode 100644 pkg/engine/testdata/init/testpkg/package-context.yaml delete mode 100644 pkg/engine/testdata/replace/Kptfile delete mode 100644 pkg/engine/testdata/replace/README.md delete mode 100644 pkg/engine/testdata/replace/bucket.yaml delete mode 100644 pkg/engine/testdata/simple-render/expected.txt delete mode 100644 pkg/engine/testdata/simple-render/simple-bucket/Kptfile delete mode 100644 pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml delete mode 100644 pkg/engine/testdata/update/local/Kptfile delete mode 100644 pkg/engine/testdata/update/local/README.md delete mode 100644 pkg/engine/testdata/update/local/namespace.yaml delete mode 100644 pkg/engine/testdata/update/local/package-context.yaml delete mode 100644 pkg/engine/testdata/update/local/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/local/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/local/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/original/Kptfile delete mode 100644 pkg/engine/testdata/update/original/README.md delete mode 100644 pkg/engine/testdata/update/original/namespace.yaml delete mode 100644 pkg/engine/testdata/update/original/package-context.yaml delete mode 100644 pkg/engine/testdata/update/original/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/original/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/original/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/updated/Kptfile delete mode 100644 pkg/engine/testdata/update/updated/README.md delete mode 100644 pkg/engine/testdata/update/updated/namespace.yaml delete mode 100644 pkg/engine/testdata/update/updated/package-context.yaml delete mode 100644 pkg/engine/testdata/update/updated/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/updated/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/updated/update-rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/upstream/Kptfile delete mode 100644 pkg/engine/testdata/update/upstream/README.md delete mode 100644 pkg/engine/testdata/update/upstream/namespace.yaml delete mode 100644 pkg/engine/testdata/update/upstream/package-context.yaml delete mode 100644 pkg/engine/testdata/update/upstream/resourcequota.yaml delete mode 100644 pkg/engine/testdata/update/upstream/rolebinding.yaml delete mode 100644 pkg/engine/testdata/update/upstream/update-rolebinding.yaml rename pkg/{engine => repository}/environment.go (80%) rename pkg/{engine => repository}/fake/packagerevision.go (53%) rename pkg/{engine => repository}/fake/repository.go (97%) create mode 100644 pkg/repository/package.go rename pkg/{engine => repository}/testing.go (86%) rename pkg/{engine => repository}/update.go (78%) rename pkg/{engine => repository}/update_test.go (97%) rename pkg/{engine => task}/builtin.go (99%) rename pkg/{engine => task}/builtin_test.go (87%) rename pkg/{engine => task}/clone.go (93%) rename pkg/{engine => task}/clone_test.go (99%) rename pkg/{engine => task}/edit.go (90%) rename pkg/{engine => task}/edit_test.go (94%) rename pkg/{engine => task}/eval.go (99%) rename pkg/{engine => task}/init.go (99%) rename pkg/{engine => task}/init_test.go (99%) rename pkg/{engine => task}/kio.go (99%) rename pkg/{engine => task}/mergekey.go (99%) rename pkg/{engine/engine_test.go => task/patch_test.go} (96%) rename pkg/{engine => task}/patchgen.go (99%) rename pkg/{engine => task}/patchgen_test.go (99%) rename pkg/{engine => task}/render.go (99%) rename pkg/{engine => task}/render_test.go (99%) rename pkg/{engine => task}/replace_test.go (96%) create mode 100644 pkg/task/taskhandler.go diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 84e9b97c..6b9918f5 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -8,24 +8,6 @@ import ( ) type Cache interface { - OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (CachedRepository, error) + OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) CloseRepository(repositorySpec *configapi.Repository, allRepos []configapi.Repository) error } - -type CachedRepository interface { - repository.Repository - RefreshCache(ctx context.Context) error -} - -type CachedPackageRevision interface { - repository.PackageRevision -} - -type CachedPackageDraft interface { - repository.PackageDraft -} - -// Remove? -type CachedPackage interface { - repository.Package -} diff --git a/pkg/cache/fake/objectnotifier.go b/pkg/cache/fake/objectnotifier.go index e955a444..f9bde5aa 100644 --- a/pkg/cache/fake/objectnotifier.go +++ b/pkg/cache/fake/objectnotifier.go @@ -15,13 +15,12 @@ package fake import ( - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/watch" ) type ObjectNotifier struct{} -func (o *ObjectNotifier) NotifyPackageRevisionChange(watch.EventType, repository.PackageRevision, meta.PackageRevisionMeta) int { +func (o *ObjectNotifier) NotifyPackageRevisionChange(watch.EventType, repository.PackageRevision) int { return 0 } diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index 1961c41d..3cd53330 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -58,7 +58,7 @@ type Cache struct { var _ cache.Cache = &Cache{} type objectNotifier interface { - NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int + NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) int } type CacheOptions struct { @@ -105,7 +105,7 @@ func getCacheKey(repositorySpec *configapi.Repository) (string, error) { } } -func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (cache.CachedRepository, error) { +func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) { ctx, span := tracer.Start(ctx, "Cache::OpenRepository", trace.WithAttributes()) defer span.End() diff --git a/pkg/cache/memory/cache_test.go b/pkg/cache/memory/cache_test.go index f7baf304..de2fbc97 100644 --- a/pkg/cache/memory/cache_test.go +++ b/pkg/cache/memory/cache_test.go @@ -25,7 +25,6 @@ import ( "github.com/google/go-cmp/cmp" api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" fakecache "github.com/nephio-project/porch/pkg/cache/fake" "github.com/nephio-project/porch/pkg/git" @@ -216,7 +215,7 @@ func TestDeletePublishedMain(t *testing.T) { } -func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) cache.CachedRepository { +func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) repository.Repository { t.Helper() tempdir := t.TempDir() diff --git a/pkg/cache/memory/draft.go b/pkg/cache/memory/draft.go index 1a9066a1..5e44b259 100644 --- a/pkg/cache/memory/draft.go +++ b/pkg/cache/memory/draft.go @@ -18,7 +18,6 @@ import ( "context" "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" ) @@ -29,7 +28,6 @@ type cachedDraft struct { } var _ repository.PackageDraft = &cachedDraft{} -var _ cache.CachedPackageDraft = &cachedDraft{} func (cd *cachedDraft) Close(ctx context.Context, version string) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cachedDraft::Close", trace.WithAttributes()) diff --git a/pkg/cache/memory/package.go b/pkg/cache/memory/package.go index 0699583b..f8528bcc 100644 --- a/pkg/cache/memory/package.go +++ b/pkg/cache/memory/package.go @@ -15,7 +15,6 @@ package memory import ( - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" ) @@ -26,7 +25,6 @@ import ( // between Git and OCI. var _ repository.Package = &cachedPackage{} -var _ cache.CachedPackage = &cachedPackage{} type cachedPackage struct { repository.Package diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index 7f0b468d..aff6a6ba 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -17,8 +17,7 @@ package memory import ( "context" - "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" + api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/pkg/repository" ) @@ -29,26 +28,60 @@ import ( // between Git and OCI. var _ repository.PackageRevision = &cachedPackageRevision{} -var _ cache.CachedPackageRevision = &cachedPackageRevision{} type cachedPackageRevision struct { repository.PackageRevision isLatestRevision bool } -func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*v1alpha1.PackageRevision, error) { - rev, err := c.PackageRevision.GetPackageRevision(ctx) +func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*api.PackageRevision, error) { + apiPR, err := c.PackageRevision.GetPackageRevision(ctx) if err != nil { return nil, err } + + apiPR.Annotations = c.GetMeta().Annotations + apiPR.Finalizers = c.GetMeta().Finalizers + apiPR.OwnerReferences = c.GetMeta().OwnerReferences + apiPR.DeletionTimestamp = c.GetMeta().DeletionTimestamp + apiPR.Labels = c.GetMeta().Labels + if c.isLatestRevision { // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(rev.Labels)) - for k, v := range rev.Labels { + labels := make(map[string]string, len(apiPR.Labels)) + for k, v := range apiPR.Labels { + labels[k] = v + } + labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue + apiPR.Labels = labels + } + + return apiPR, nil +} + +func (c *cachedPackageRevision) GetCachedPackageRevision(ctx context.Context) (*api.PackageRevision, error) { + repoPkgRev, err := c.GetPackageRevision(ctx) + if err != nil { + return nil, err + } + var isLatest bool + if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { + isLatest = true + } + repoPkgRev.Labels = c.GetMeta().Labels + if isLatest { + // copy the labels in case the cached object is being read by another go routine + labels := make(map[string]string, len(repoPkgRev.Labels)) + for k, v := range repoPkgRev.Labels { labels[k] = v } - labels[v1alpha1.LatestPackageRevisionKey] = v1alpha1.LatestPackageRevisionValue - rev.Labels = labels + labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue + repoPkgRev.Labels = labels } - return rev, nil + repoPkgRev.Annotations = c.GetMeta().Annotations + repoPkgRev.Finalizers = c.GetMeta().Finalizers + repoPkgRev.OwnerReferences = c.GetMeta().OwnerReferences + repoPkgRev.DeletionTimestamp = c.GetMeta().DeletionTimestamp + + return repoPkgRev, nil } diff --git a/pkg/cache/memory/repository.go b/pkg/cache/memory/repository.go index b3874e0d..a7584a7c 100644 --- a/pkg/cache/memory/repository.go +++ b/pkg/cache/memory/repository.go @@ -22,13 +22,12 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/git" "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" - apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" @@ -43,7 +42,6 @@ var tracer = otel.Tracer("cache") // between Git and OCI. var _ repository.Repository = &cachedRepository{} -var _ cache.CachedRepository = &cachedRepository{} type cachedRepository struct { id string @@ -85,7 +83,7 @@ func newRepository(id string, repoSpec *configapi.Repository, repo repository.Re return r } -func (r *cachedRepository) RefreshCache(ctx context.Context) error { +func (r *cachedRepository) Refresh(ctx context.Context) error { _, _, err := r.refreshAllCachedPackages(ctx) @@ -261,7 +259,7 @@ func (r *cachedRepository) createMainPackageRevision(ctx context.Context, update // Create the package if it doesn't exist _, err := r.metadataStore.Get(ctx, pkgRevMetaNN) - if apierrors.IsNotFound(err) { + if errors.IsNotFound(err) { pkgRevMeta := meta.PackageRevisionMeta{ Name: updatedMain.KubeObjectName(), Namespace: updatedMain.KubeObjectNamespace(), @@ -346,19 +344,15 @@ func (r *cachedRepository) Close() error { // the repository, so we have to just delete the PackageRevision regardless of any // finalizers. klog.Infof("repo %s: deleting packagerev %s/%s because repository is closed", r.id, nn.Namespace, nn.Name) - pkgRevMeta, err := r.metadataStore.Delete(context.TODO(), nn, true) + _, err := r.metadataStore.Delete(context.TODO(), nn, true) if err != nil { // There isn't much use in returning an error here, so we just log it // and create a PackageRevisionMeta with just name and namespace. This // makes sure that the Delete event is sent. klog.Warningf("repo %s: error deleting packagerev for %s: %v", r.id, nn.Name, err) - pkgRevMeta = meta.PackageRevisionMeta{ - Name: nn.Name, - Namespace: nn.Namespace, - } } klog.Infof("repo %s: successfully deleted packagerev %s/%s", r.id, nn.Namespace, nn.Name) - sent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, pr, pkgRevMeta) + sent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, pr) } klog.Infof("repo %s: sent %d notifications for %d package revisions during close", r.id, sent, len(r.cachedPackageRevisions)) return r.repo.Close() @@ -474,7 +468,7 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re Name: prm.Name, Namespace: prm.Namespace, }, true); err != nil { - if !apierrors.IsNotFound(err) { + if !errors.IsNotFound(err) { // This will be retried the next time the sync runs. klog.Warningf("repo %s: unable to delete PackageRev CR for %s/%s: %v", r.id, prm.Name, prm.Namespace, err) @@ -488,15 +482,11 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re modSent := 0 for kname, newPackage := range newPackageRevisionNames { oldPackage := oldPackageRevisionNames[kname] - metaPackage, found := existingPkgRevCRsMap[newPackage.KubeObjectName()] - if !found { - klog.Warningf("no PackageRev CR found for PackageRevision %s", newPackage.KubeObjectName()) - } if oldPackage == nil { - addSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Added, newPackage, metaPackage) + addSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Added, newPackage) } else { if oldPackage.ResourceVersion() != newPackage.ResourceVersion() { - modSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Modified, newPackage, metaPackage) + modSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Modified, newPackage) } } } @@ -529,17 +519,7 @@ func (r *cachedRepository) refreshAllCachedPackages(ctx context.Context) (map[re } klog.Infof("repo %s: deleting PackageRev %s/%s because PackageRevision was removed from SoT", r.id, nn.Namespace, nn.Name) - metaPackage, err := r.metadataStore.Delete(ctx, nn, true) - if err != nil { - if !apierrors.IsNotFound(err) { - klog.Warningf("repo %s: error deleting PkgRevMeta %s: %v", r.id, nn, err) - } - metaPackage = meta.PackageRevisionMeta{ - Name: nn.Name, - Namespace: nn.Namespace, - } - } - delSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, oldPackage, metaPackage) + delSent += r.objectNotifier.NotifyPackageRevisionChange(watch.Deleted, oldPackage) } } klog.Infof("repo %s: addSent %d, modSent %d, delSent for %d old and %d new repo packages", r.id, addSent, modSent, len(oldPackageRevisionNames), len(newPackageRevisionNames)) diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index f703e59c..cdb3ca1c 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -57,6 +57,7 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac latest[currentKey.Package] = current } } + // Mark the winners as latest for _, v := range latest { v.isLatestRevision = true diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go deleted file mode 100644 index c040bddc..00000000 --- a/pkg/engine/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2022 The kpt and Nephio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Implementation of package orchestration engine -// The engine is independent of k8s runtime (k8s apiserver integration is in apiserver module) -package engine diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index ac430479..84d1f14f 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -15,7 +15,6 @@ package engine import ( - "bytes" "context" "errors" "fmt" @@ -24,29 +23,16 @@ import ( api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/internal/kpt/builtins" - "github.com/nephio-project/porch/internal/kpt/fnruntime" cache "github.com/nephio-project/porch/pkg/cache" - "github.com/nephio-project/porch/pkg/kpt" - kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" - "github.com/nephio-project/porch/pkg/kpt/fn" "github.com/nephio-project/porch/pkg/meta" - "github.com/nephio-project/porch/pkg/objects" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/task" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/kustomize/kyaml/comments" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" ) var tracer = otel.Tracer("engine") @@ -59,79 +45,17 @@ type CaDEngine interface { // ObjectCache() is a cache of all our objects. ObjectCache() WatcherManager - UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevisionResources) (*PackageRevision, *api.RenderStatus, error) + UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage repository.PackageRevision, old, new *api.PackageRevisionResources) (repository.PackageRevision, *api.RenderStatus, error) - ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]*PackageRevision, error) - CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) - UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) - DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *PackageRevision) error + ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]repository.PackageRevision, error) + CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) + UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage repository.PackageRevision, old, new *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) + DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj repository.PackageRevision) error - ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]*Package, error) - CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (*Package, error) - UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package, old, new *api.PorchPackage) (*Package, error) - DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *Package) error -} - -type Package struct { - repoPackage repository.Package -} - -func (p *Package) GetPackage() *api.PorchPackage { - return p.repoPackage.GetPackage() -} - -func (p *Package) KubeObjectName() string { - return p.repoPackage.KubeObjectName() -} - -// TODO: This is a bit awkward, and we should see if there is a way to avoid -// having to expose this function. Any functionality that requires creating new -// engine.PackageRevision resources should be in the engine package. -func ToPackageRevision(pkgRev repository.PackageRevision, pkgRevMeta meta.PackageRevisionMeta) *PackageRevision { - return &PackageRevision{ - repoPackageRevision: pkgRev, - packageRevisionMeta: pkgRevMeta, - } -} - -type PackageRevision struct { - repoPackageRevision repository.PackageRevision - packageRevisionMeta meta.PackageRevisionMeta -} - -func (p *PackageRevision) GetPackageRevision(ctx context.Context) (*api.PackageRevision, error) { - repoPkgRev, err := p.repoPackageRevision.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - var isLatest bool - if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { - isLatest = true - } - repoPkgRev.Labels = p.packageRevisionMeta.Labels - if isLatest { - // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(repoPkgRev.Labels)) - for k, v := range repoPkgRev.Labels { - labels[k] = v - } - labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue - repoPkgRev.Labels = labels - } - repoPkgRev.Annotations = p.packageRevisionMeta.Annotations - repoPkgRev.Finalizers = p.packageRevisionMeta.Finalizers - repoPkgRev.OwnerReferences = p.packageRevisionMeta.OwnerReferences - repoPkgRev.DeletionTimestamp = p.packageRevisionMeta.DeletionTimestamp - - return repoPkgRev, nil -} - -func (p *PackageRevision) KubeObjectName() string { - return p.repoPackageRevision.KubeObjectName() -} - -func (p *PackageRevision) GetResources(ctx context.Context) (*api.PackageRevisionResources, error) { - return p.repoPackageRevision.GetResources(ctx) + ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]repository.Package, error) + CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (repository.Package, error) + UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage repository.Package, old, new *api.PorchPackage) (repository.Package, error) + DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, obj repository.Package) error } func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { @@ -147,23 +71,14 @@ func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { type cadEngine struct { cache cache.Cache - // runnerOptionsResolver returns the RunnerOptions for function execution in the specified namespace. - runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions - - runtime fn.FunctionRuntime - credentialResolver repository.CredentialResolver - referenceResolver ReferenceResolver - userInfoProvider repository.UserInfoProvider - metadataStore meta.MetadataStore - watcherManager *watcherManager + userInfoProvider repository.UserInfoProvider + metadataStore meta.MetadataStore + watcherManager *watcherManager + taskHandler task.TaskHandler } var _ CaDEngine = &cadEngine{} -type mutation interface { - Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) -} - // ObjectCache is a cache of all our objects. func (cad *cadEngine) ObjectCache() WatcherManager { return cad.watcherManager @@ -176,7 +91,7 @@ func (cad *cadEngine) OpenRepository(ctx context.Context, repositorySpec *config return cad.cache.OpenRepository(ctx, repositorySpec) } -func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]*PackageRevision, error) { +func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageRevisionFilter) ([]repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::ListPackageRevisions", trace.WithAttributes()) defer span.End() @@ -189,7 +104,7 @@ func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec * return nil, err } - var packageRevisions []*PackageRevision + var packageRevisions []repository.PackageRevision for _, pr := range pkgRevs { pkgRevMeta, err := cad.metadataStore.Get(ctx, types.NamespacedName{ Name: pr.KubeObjectName(), @@ -203,63 +118,17 @@ func (cad *cadEngine) ListPackageRevisions(ctx context.Context, repositorySpec * } return nil, err } - packageRevisions = append(packageRevisions, &PackageRevision{ - repoPackageRevision: pr, - packageRevisionMeta: pkgRevMeta, - }) + pr.SetMeta(pkgRevMeta) + packageRevisions = append(packageRevisions, pr) } return packageRevisions, nil } -func buildPackageConfig(ctx context.Context, obj *api.PackageRevision, parent *PackageRevision) (*builtins.PackageConfig, error) { - config := &builtins.PackageConfig{} - - parentPath := "" - - var parentConfig *unstructured.Unstructured - if parent != nil { - parentObj, err := parent.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - parentPath = parentObj.Spec.PackageName - - resources, err := parent.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("error getting resources from parent package %q: %w", parentObj.Name, err) - } - configMapObj, err := ExtractContextConfigMap(resources.Spec.Resources) - if err != nil { - return nil, fmt.Errorf("error getting configuration from parent package %q: %w", parentObj.Name, err) - } - parentConfig = configMapObj - - if parentConfig != nil { - // TODO: Should we support kinds other than configmaps? - var parentConfigMap corev1.ConfigMap - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(parentConfig.Object, &parentConfigMap); err != nil { - return nil, fmt.Errorf("error parsing ConfigMap from parent configuration: %w", err) - } - if s := parentConfigMap.Data[builtins.ConfigKeyPackagePath]; s != "" { - parentPath = s + "/" + parentPath - } - } - } - - if parentPath == "" { - config.PackagePath = obj.Spec.PackageName - } else { - config.PackagePath = parentPath + "/" + obj.Spec.PackageName - } - - return config, nil -} - -func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) { +func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::CreatePackageRevision", trace.WithAttributes()) defer span.End() - packageConfig, err := buildPackageConfig(ctx, obj, parent) + packageConfig, err := repository.BuildPackageConfig(ctx, obj, parent) if err != nil { return nil, err } @@ -302,7 +171,7 @@ func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj * return nil, err } - if err := cad.applyTasks(ctx, draft, repositoryObj, obj, packageConfig); err != nil { + if err := cad.taskHandler.ApplyTasks(ctx, draft, repositoryObj, obj, packageConfig); err != nil { return nil, err } @@ -327,12 +196,10 @@ func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj * if err != nil { return nil, err } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Added, repoPkgRev, pkgRevMeta) + repoPkgRev.SetMeta(pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Added, repoPkgRev) klog.Infof("engine: sent %d for new PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return &PackageRevision{ - repoPackageRevision: repoPkgRev, - packageRevisionMeta: pkgRevMeta, - }, nil + return repoPkgRev, nil } // The workspaceName must be unique, because it used to generate the package revision's metadata.name. @@ -347,141 +214,7 @@ func ensureUniqueWorkspaceName(obj *api.PackageRevision, existingRevs []reposito return nil } -func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { - for _, tt := range oneOf { - if taskType == tt { - return true - } - } - return false -} - -func (cad *cadEngine) applyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { - var mutations []mutation - - // Unless first task is Init or Clone, insert Init to create an empty package. - tasks := obj.Spec.Tasks - if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { - mutations = append(mutations, &initPackageMutation{ - name: obj.Spec.PackageName, - task: &api.Task{ - Init: &api.PackageInitTaskSpec{ - Subpackage: "", - Description: fmt.Sprintf("%s description", obj.Spec.PackageName), - }, - }, - }) - } - - for i := range tasks { - task := &tasks[i] - mutation, err := cad.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) - if err != nil { - return err - } - mutations = append(mutations, mutation) - } - - // Render package after creation. - mutations = cad.conditionalAddRender(obj, mutations) - - baseResources := repository.PackageResources{} - if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { - return err - } - - return nil -} - -type RepositoryOpener interface { - OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) -} - -func (cad *cadEngine) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { - switch task.Type { - case api.TaskTypeInit: - if task.Init == nil { - return nil, fmt.Errorf("init not set for task of type %q", task.Type) - } - return &initPackageMutation{ - name: obj.Spec.PackageName, - task: task, - }, nil - case api.TaskTypeClone: - if task.Clone == nil { - return nil, fmt.Errorf("clone not set for task of type %q", task.Type) - } - return &clonePackageMutation{ - task: task, - namespace: obj.Namespace, - name: obj.Spec.PackageName, - isDeployment: isDeployment, - repoOpener: cad, - credentialResolver: cad.credentialResolver, - referenceResolver: cad.referenceResolver, - packageConfig: packageConfig, - }, nil - - case api.TaskTypeUpdate: - if task.Update == nil { - return nil, fmt.Errorf("update not set for task of type %q", task.Type) - } - cloneTask := findCloneTask(obj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) - } - return &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: task, - namespace: obj.Namespace, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - pkgName: obj.Spec.PackageName, - }, nil - - case api.TaskTypePatch: - return buildPatchMutation(ctx, task) - - case api.TaskTypeEdit: - if task.Edit == nil { - return nil, fmt.Errorf("edit not set for task of type %q", task.Type) - } - return &editPackageMutation{ - task: task, - namespace: obj.Namespace, - packageName: obj.Spec.PackageName, - repositoryName: obj.Spec.RepositoryName, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - }, nil - - case api.TaskTypeEval: - if task.Eval == nil { - return nil, fmt.Errorf("eval not set for task of type %q", task.Type) - } - // TODO: We should find a different way to do this. Probably a separate - // task for render. - if task.Eval.Image == "render" { - runnerOptions := cad.runnerOptionsResolver(obj.Namespace) - return &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }, nil - } else { - runnerOptions := cad.runnerOptionsResolver(obj.Namespace) - return &evalFunctionMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - task: task, - }, nil - } - - default: - return nil, fmt.Errorf("task of type %q not supported", task.Type) - } -} - -func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, oldPackage *PackageRevision, oldObj, newObj *api.PackageRevision, parent *PackageRevision) (*PackageRevision, error) { +func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, repositoryObj *configapi.Repository, repoPr repository.PackageRevision, oldObj, newObj *api.PackageRevision, parent repository.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::UpdatePackageRevision", trace.WithAttributes()) defer span.End() @@ -491,7 +224,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, } if newRV != oldObj.GetResourceVersion() { - return nil, apierrors.NewConflict(api.Resource("packagerevisions"), oldObj.GetName(), errors.New(OptimisticLockErrorMsg)) + return nil, apierrors.NewConflict(api.Resource("packagerevisions"), oldObj.GetName(), fmt.Errorf(OptimisticLockErrorMsg)) } repo, err := cad.cache.OpenRepository(ctx, repositoryObj) @@ -501,7 +234,7 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, // Check if the PackageRevision is in the terminating state and // and this request removes the last finalizer. - repoPkgRev := oldPackage.repoPackageRevision + repoPkgRev := repoPr pkgRevMetaNN := types.NamespacedName{ Name: repoPkgRev.KubeObjectName(), Namespace: repoPkgRev.KubeObjectNamespace(), @@ -510,145 +243,62 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, if err != nil { return nil, err } + repoPkgRev.SetMeta(pkgRevMeta) // If this is in the terminating state and we are removing the last finalizer, // we delete the resource instead of updating it. if pkgRevMeta.DeletionTimestamp != nil && len(newObj.Finalizers) == 0 { - if err := cad.deletePackageRevision(ctx, repo, repoPkgRev, pkgRevMeta); err != nil { + if err := cad.deletePackageRevision(ctx, repo, repoPkgRev); err != nil { return nil, err } - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil + return repoPkgRev, nil } // Validate package lifecycle. Can only update a draft. switch lifecycle := oldObj.Spec.Lifecycle; lifecycle { - default: - return nil, fmt.Errorf("invalid original lifecycle value: %q", lifecycle) + case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed: // Draft or proposed can be updated. + case api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: // Only metadata (currently labels and annotations) and lifecycle can be updated for published packages. if oldObj.Spec.Lifecycle != newObj.Spec.Lifecycle { - if err := oldPackage.repoPackageRevision.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { + if err := repoPr.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { return nil, err } } - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) if err != nil { return nil, err } - - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) klog.Infof("engine: sent %d for updated PackageRevision metadata %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil - } - switch lifecycle := newObj.Spec.Lifecycle; lifecycle { - default: - return nil, fmt.Errorf("invalid desired lifecycle value: %q", lifecycle) - case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed, api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: - // These values are ok - } - - if isRecloneAndReplay(oldObj, newObj) { - packageConfig, err := buildPackageConfig(ctx, newObj, parent) - if err != nil { - return nil, err - } - repoPkgRev, err := cad.recloneAndReplay(ctx, version, repo, repositoryObj, newObj, packageConfig) - if err != nil { - return nil, err - } - - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) - if err != nil { - return nil, err - } + return repoPkgRev, nil - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) - klog.Infof("engine: sent %d for reclone and replay PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil - } - - var mutations []mutation - if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { - return nil, fmt.Errorf("removing tasks is not yet supported") - } - for i := range oldObj.Spec.Tasks { - oldTask := &oldObj.Spec.Tasks[i] - newTask := &newObj.Spec.Tasks[i] - if oldTask.Type != newTask.Type { - return nil, fmt.Errorf("changing task types is not yet supported") - } + default: + return nil, fmt.Errorf("invalid original lifecycle value: %q", lifecycle) } - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { - return nil, fmt.Errorf("can only append one task at a time") - } - newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] - if newTask.Type != api.TaskTypeUpdate { - return nil, fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) - } - if newTask.Update == nil { - return nil, fmt.Errorf("update not set for updateTask of type %q", newTask.Type) - } + switch lifecycle := newObj.Spec.Lifecycle; lifecycle { - cloneTask := findCloneTask(oldObj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) - } + case api.PackageRevisionLifecycleDraft, api.PackageRevisionLifecycleProposed, api.PackageRevisionLifecyclePublished, api.PackageRevisionLifecycleDeletionProposed: + // These values are ok - mutation := &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: &newTask, - repoOpener: cad, - referenceResolver: cad.referenceResolver, - namespace: repositoryObj.Namespace, - pkgName: oldObj.GetName(), - } - mutations = append(mutations, mutation) + default: + return nil, fmt.Errorf("invalid desired lifecycle value: %q", lifecycle) } - // Re-render if we are making changes. - mutations = cad.conditionalAddRender(newObj, mutations) - - draft, err := repo.UpdatePackageRevision(ctx, oldPackage.repoPackageRevision) - if err != nil { - return nil, err + if isRecloneAndReplay(oldObj, newObj) { + return cad.RecloneAndReplay(ctx, parent, version, repo, repositoryObj, newObj) } - // If any of the fields in the API that are projections from the Kptfile - // must be updated in the Kptfile as well. - kfPatchTask, created, err := createKptfilePatchTask(ctx, oldPackage.repoPackageRevision, newObj) + // Do we need to clean up this draft later? + draft, err := repo.UpdatePackageRevision(ctx, repoPr) if err != nil { return nil, err } - if created { - kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) - if err != nil { - return nil, err - } - mutations = append(mutations, kfPatchMutation) - } - - // Re-render if we are making changes. - mutations = cad.conditionalAddRender(newObj, mutations) - // TODO: Handle the case if alongside lifecycle change, tasks are changed too. - // Update package contents only if the package is in draft state - if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { - apiResources, err := oldPackage.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: apiResources.Spec.Resources, - } - - if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { - return nil, err - } - } + cad.taskHandler.DoPRMutations(ctx, repositoryObj.Namespace, repoPr, oldObj, newObj, draft) if err := draft.UpdateLifecycle(ctx, newObj.Spec.Lifecycle); err != nil { return nil, err @@ -660,18 +310,18 @@ func (cad *cadEngine) UpdatePackageRevision(ctx context.Context, version string, return nil, err } - pkgRevMeta, err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) if err != nil { return nil, err } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) klog.Infof("engine: sent %d for updated PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) - return ToPackageRevision(repoPkgRev, pkgRevMeta), nil + return repoPkgRev, nil } -func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repository.PackageRevision, apiPkgRev *api.PackageRevision) (meta.PackageRevisionMeta, error) { +func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repository.PackageRevision, apiPkgRev *api.PackageRevision) error { pkgRevMeta := meta.PackageRevisionMeta{ Name: repoPkgRev.KubeObjectName(), Namespace: repoPkgRev.KubeObjectNamespace(), @@ -680,118 +330,18 @@ func (cad *cadEngine) updatePkgRevMeta(ctx context.Context, repoPkgRev repositor Finalizers: apiPkgRev.Finalizers, OwnerReferences: apiPkgRev.OwnerReferences, } - return cad.metadataStore.Update(ctx, pkgRevMeta) -} -func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { - kf, err := oldPackage.GetKptfile(ctx) - if err != nil { - return nil, false, err - } + repoPkgRev.SetMeta(pkgRevMeta) - var orgKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - orgKfString = buf.String() - } - - var readinessGates []kptfile.ReadinessGate - for _, rg := range newObj.Spec.ReadinessGates { - readinessGates = append(readinessGates, kptfile.ReadinessGate{ - ConditionType: rg.ConditionType, - }) - } - - var conditions []kptfile.Condition - for _, c := range newObj.Status.Conditions { - conditions = append(conditions, kptfile.Condition{ - Type: c.Type, - Status: convertStatusToKptfile(c.Status), - Reason: c.Reason, - Message: c.Message, - }) - } - - if kf.Info == nil && len(readinessGates) > 0 { - kf.Info = &kptfile.PackageInfo{} - } - if len(readinessGates) > 0 { - kf.Info.ReadinessGates = readinessGates - } - - if kf.Status == nil && len(conditions) > 0 { - kf.Status = &kptfile.Status{} - } - if len(conditions) > 0 { - kf.Status.Conditions = conditions - } - - var newKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - newKfString = buf.String() - } - patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) - if err != nil { - return nil, false, err - } - // If patch is empty, don't create a Task. - if patchSpec.Contents == "" { - return nil, false, nil - } - - return &api.Task{ - Type: api.TaskTypePatch, - Patch: &api.PackagePatchTaskSpec{ - Patches: []api.PatchSpec{ - patchSpec, - }, - }, - }, true, nil -} - -func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { - switch s { - case api.ConditionTrue: - return kptfile.ConditionTrue - case api.ConditionFalse: - return kptfile.ConditionFalse - case api.ConditionUnknown: - return kptfile.ConditionUnknown - default: - panic(fmt.Errorf("unknown condition status: %v", s)) - } -} - -// conditionalAddRender adds a render mutation to the end of the mutations slice if the last -// entry is not already a render mutation. -func (cad *cadEngine) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { - if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { - return mutations + if storedPkgRevMeta, err := cad.metadataStore.Update(ctx, pkgRevMeta); err == nil { + repoPkgRev.SetMeta(storedPkgRevMeta) + return nil + } else { + return err } - - runnerOptions := cad.runnerOptionsResolver(subject.GetNamespace()) - - return append(mutations, &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }) } -func isRenderMutation(m mutation) bool { - _, isRender := m.(*renderPackageMutation) - return isRender -} - -func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision) error { +func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj *configapi.Repository, pr2Del repository.PackageRevision) error { ctx, span := tracer.Start(ctx, "cadEngine::DeletePackageRevision", trace.WithAttributes()) defer span.End() @@ -807,8 +357,8 @@ func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj * // But we only delete the PackageRevision from the repo once all finalizers // have been removed. namespacedName := types.NamespacedName{ - Name: oldPackage.repoPackageRevision.KubeObjectName(), - Namespace: oldPackage.repoPackageRevision.KubeObjectNamespace(), + Name: pr2Del.KubeObjectName(), + Namespace: pr2Del.KubeObjectNamespace(), } pkgRevMeta, err := cad.metadataStore.Delete(ctx, namespacedName, false) if err != nil { @@ -816,17 +366,17 @@ func (cad *cadEngine) DeletePackageRevision(ctx context.Context, repositoryObj * } if len(pkgRevMeta.Finalizers) > 0 { - klog.Infof("PackageRevision %s deleted, but still have finalizers: %s", oldPackage.KubeObjectName(), strings.Join(pkgRevMeta.Finalizers, ",")) - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, oldPackage.repoPackageRevision, oldPackage.packageRevisionMeta) - klog.Infof("engine: sent %d modified for deleted PackageRevision %s/%s with finalizers", sent, oldPackage.repoPackageRevision.KubeObjectNamespace(), oldPackage.KubeObjectName()) + klog.Infof("PackageRevision %s deleted, but still have finalizers: %s", pr2Del.KubeObjectName(), strings.Join(pkgRevMeta.Finalizers, ",")) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, pr2Del) + klog.Infof("engine: sent %d modified for deleted PackageRevision %s/%s with finalizers", sent, pr2Del.KubeObjectNamespace(), pr2Del.KubeObjectName()) return nil } - klog.Infof("PackageRevision %s deleted for real since no finalizers", oldPackage.KubeObjectName()) + klog.Infof("PackageRevision %s deleted for real since no finalizers", pr2Del.KubeObjectName()) - return cad.deletePackageRevision(ctx, repo, oldPackage.repoPackageRevision, oldPackage.packageRevisionMeta) + return cad.deletePackageRevision(ctx, repo, pr2Del) } -func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository.Repository, repoPkgRev repository.PackageRevision, pkgRevMeta meta.PackageRevisionMeta) error { +func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository.Repository, repoPkgRev repository.PackageRevision) error { ctx, span := tracer.Start(ctx, "cadEngine::deletePackageRevision", trace.WithAttributes()) defer span.End() @@ -835,8 +385,8 @@ func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository } nn := types.NamespacedName{ - Name: pkgRevMeta.Name, - Namespace: pkgRevMeta.Namespace, + Name: repoPkgRev.GetMeta().Name, + Namespace: repoPkgRev.GetMeta().Namespace, } if _, err := cad.metadataStore.Delete(ctx, nn, true); err != nil { // If this fails, the CR will be cleaned up by the background job. @@ -845,12 +395,12 @@ func (cad *cadEngine) deletePackageRevision(ctx context.Context, repo repository } } - sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Deleted, repoPkgRev, pkgRevMeta) + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Deleted, repoPkgRev) klog.Infof("engine: sent %d for deleted PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) return nil } -func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]*Package, error) { +func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configapi.Repository, filter repository.ListPackageFilter) ([]repository.Package, error) { ctx, span := tracer.Start(ctx, "cadEngine::ListPackages", trace.WithAttributes()) defer span.End() @@ -863,17 +413,15 @@ func (cad *cadEngine) ListPackages(ctx context.Context, repositorySpec *configap if err != nil { return nil, err } - var packages []*Package + var packages []repository.Package for _, p := range pkgs { - packages = append(packages, &Package{ - repoPackage: p, - }) + packages = append(packages, p) } return packages, nil } -func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (*Package, error) { +func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configapi.Repository, obj *api.PorchPackage) (repository.Package, error) { ctx, span := tracer.Start(ctx, "cadEngine::CreatePackage", trace.WithAttributes()) defer span.End() @@ -886,21 +434,19 @@ func (cad *cadEngine) CreatePackage(ctx context.Context, repositoryObj *configap return nil, err } - return &Package{ - repoPackage: pkg, - }, nil + return pkg, nil } -func (cad *cadEngine) UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package, oldObj, newObj *api.PorchPackage) (*Package, error) { +func (cad *cadEngine) UpdatePackage(ctx context.Context, repositoryObj *configapi.Repository, pkg2Update repository.Package, oldObj, newObj *api.PorchPackage) (repository.Package, error) { _, span := tracer.Start(ctx, "cadEngine::UpdatePackage", trace.WithAttributes()) defer span.End() // TODO - var pkg *Package + var pkg repository.Package return pkg, errors.New("updating packages is not yet supported") } -func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *Package) error { +func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configapi.Repository, pkg2Del repository.Package) error { ctx, span := tracer.Start(ctx, "cadEngine::DeletePackage", trace.WithAttributes()) defer span.End() @@ -909,29 +455,29 @@ func (cad *cadEngine) DeletePackage(ctx context.Context, repositoryObj *configap return err } - if err := repo.DeletePackage(ctx, oldPackage.repoPackage); err != nil { + if err := repo.DeletePackage(ctx, pkg2Del); err != nil { return err } return nil } -func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, oldPackage *PackageRevision, old, new *api.PackageRevisionResources) (*PackageRevision, *api.RenderStatus, error) { +func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj *configapi.Repository, pr2Update repository.PackageRevision, oldRes, newRes *api.PackageRevisionResources) (repository.PackageRevision, *api.RenderStatus, error) { ctx, span := tracer.Start(ctx, "cadEngine::UpdatePackageResources", trace.WithAttributes()) defer span.End() - rev, err := oldPackage.repoPackageRevision.GetPackageRevision(ctx) + rev, err := pr2Update.GetPackageRevision(ctx) if err != nil { return nil, nil, err } - newRV := new.GetResourceVersion() + newRV := newRes.GetResourceVersion() if len(newRV) == 0 { return nil, nil, fmt.Errorf("resourceVersion must be specified for an update") } - if newRV != old.GetResourceVersion() { - return nil, nil, apierrors.NewConflict(api.Resource("packagerevisionresources"), old.GetName(), errors.New(OptimisticLockErrorMsg)) + if newRV != oldRes.GetResourceVersion() { + return nil, nil, apierrors.NewConflict(api.Resource("packagerevisionresources"), oldRes.GetName(), errors.New(OptimisticLockErrorMsg)) } // Validate package lifecycle. Can only update a draft. @@ -949,315 +495,19 @@ func (cad *cadEngine) UpdatePackageResources(ctx context.Context, repositoryObj if err != nil { return nil, nil, err } - draft, err := repo.UpdatePackageRevision(ctx, oldPackage.repoPackageRevision) + draft, err := repo.UpdatePackageRevision(ctx, pr2Update) if err != nil { return nil, nil, err } - runnerOptions := cad.runnerOptionsResolver(old.GetNamespace()) - - mutations := []mutation{ - &mutationReplaceResources{ - newResources: new, - oldResources: old, - }, - } - prevResources, err := oldPackage.repoPackageRevision.GetResources(ctx) - if err != nil { - return nil, nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: prevResources.Spec.Resources, - } - - appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) - if err != nil { - return nil, nil, err - } - - var renderStatus *api.RenderStatus - if len(appliedResources.Contents) > 0 { - // render the package - // Render failure will not fail the overall API operation. - // The render error and result is captured as part of renderStatus above - // and is returned in packageresourceresources API's status field. We continue with - // saving the non-rendered resources to avoid losing user's changes. - // and supress this err. - _, renderStatus, _ = applyResourceMutations(ctx, - draft, - appliedResources, - []mutation{&renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: cad.runtime, - }}) - } else { - renderStatus = nil - } + renderStatus, err := cad.taskHandler.DoPRResourceMutations(ctx, pr2Update, draft, oldRes, newRes) // No lifecycle change when updating package resources; updates are done. repoPkgRev, err := draft.Close(ctx, "") if err != nil { return nil, renderStatus, err } - return &PackageRevision{ - repoPackageRevision: repoPkgRev, - }, renderStatus, nil -} - -// applyResourceMutations mutates the resources and returns the most recent renderResult. -func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { - var lastApplied mutation - for _, m := range mutations { - updatedResources, taskResult, err := m.Apply(ctx, baseResources) - if taskResult == nil && err == nil { - // a nil taskResult means nothing changed - continue - } - - var task *api.Task - if taskResult != nil { - task = taskResult.Task - } - if taskResult != nil && task.Type == api.TaskTypeEval { - renderStatus = taskResult.RenderStatus - } - if err != nil { - return updatedResources, renderStatus, err - } - - // if the last applied mutation was a render mutation, and so is this one, skip it - if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { - continue - } - lastApplied = m - - if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ - Spec: api.PackageRevisionResourcesSpec{ - Resources: updatedResources.Contents, - }, - }, task); err != nil { - return updatedResources, renderStatus, err - } - baseResources = updatedResources - applied = updatedResources - } - - return applied, renderStatus, nil -} - -type updatePackageMutation struct { - cloneTask *api.Task - updateTask *api.Task - repoOpener RepositoryOpener - referenceResolver ReferenceResolver - namespace string - pkgName string -} - -func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) - defer span.End() - - currUpstreamPkgRef, err := m.currUpstream() - if err != nil { - return repository.PackageResources{}, nil, err - } - - targetUpstream := m.updateTask.Update.Upstream - if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { - return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") - } - - originalResources, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, - }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", - m.pkgName, *currUpstreamPkgRef) - } - - upstreamRevision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, - }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) - } - upstreamResources, err := upstreamRevision.GetResources(ctx) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) - } - - klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", - m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) - - // May be have packageUpdater part of engine to make it easy for testing ? - updatedResources, err := (&defaultPackageUpdater{}).Update(ctx, - resources, - repository.PackageResources{ - Contents: originalResources.Spec.Resources, - }, - repository.PackageResources{ - Contents: upstreamResources.Spec.Resources, - }) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) - } - - newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) - } - if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) - } - - // ensure merge-key comment is added to newly added resources. - result, err := ensureMergeKey(ctx, updatedResources) - if err != nil { - klog.Infof("failed to add merge key comments: %v", err) - } - return result, &api.TaskResult{Task: m.updateTask}, nil -} - -// Currently assumption is that downstream packages will be forked from a porch package. -// As per current implementation, upstream package ref is stored in a new update task but this may -// change so the logic of figuring out current upstream will live in this function. -func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { - if m.cloneTask == nil || m.cloneTask.Clone == nil { - return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) - } - upstream := m.cloneTask.Clone.Upstream - if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { - return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) - } - return upstream.UpstreamRef, nil -} - -func findCloneTask(pr *api.PackageRevision) *api.Task { - if len(pr.Spec.Tasks) == 0 { - return nil - } - firstTask := pr.Spec.Tasks[0] - if firstTask.Type == api.TaskTypeClone { - return &firstTask - } - return nil -} - -type mutationReplaceResources struct { - newResources *api.PackageRevisionResources - oldResources *api.PackageRevisionResources -} - -func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) - defer span.End() - - patch := &api.PackagePatchTaskSpec{} - - old := resources.Contents - new, err := healConfig(old, m.newResources.Spec.Resources) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) - } - - for k, newV := range new { - oldV, ok := old[k] - // New config or changed config - if !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeCreateFile, - Contents: newV, - } - patch.Patches = append(patch.Patches, patchSpec) - } else if newV != oldV { - patchSpec, err := GeneratePatch(k, oldV, newV) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) - } - if patchSpec.Contents == "" { - continue - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - for k := range old { - // Deleted config - if _, ok := new[k]; !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeDeleteFile, - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - // If patch is empty, don't create a Task. - var taskResult *api.TaskResult - if len(patch.Patches) > 0 { - taskResult = &api.TaskResult{ - Task: &api.Task{ - Type: api.TaskTypePatch, - Patch: patch, - }, - } - } - return repository.PackageResources{Contents: new}, taskResult, nil -} - -func healConfig(old, new map[string]string) (map[string]string, error) { - // Copy comments from old config to new - oldResources, err := (&packageReader{ - input: repository.PackageResources{Contents: old}, - extra: map[string]string{}, - }).Read() - if err != nil { - return nil, fmt.Errorf("failed to read old packge resources: %w", err) - } - - var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { - for _, n := range r { - for _, original := range oldResources { - if n.GetNamespace() == original.GetNamespace() && - n.GetName() == original.GetName() && - n.GetApiVersion() == original.GetApiVersion() && - n.GetKind() == original.GetKind() { - comments.CopyComments(original, n) - } - } - } - return r, nil - } - - out := &packageWriter{ - output: repository.PackageResources{ - Contents: map[string]string{}, - }, - } - - extra := map[string]string{} - - if err := (kio.Pipeline{ - Inputs: []kio.Reader{&packageReader{ - input: repository.PackageResources{Contents: new}, - extra: extra, - }}, - Filters: []kio.Filter{filter}, - Outputs: []kio.Writer{out}, - ContinueOnEmptyResult: true, - }).Execute(); err != nil { - return nil, err - } - - healed := out.output.Contents - - for k, v := range extra { - healed[k] = v - } - - return healed, nil + return repoPkgRev, renderStatus, nil } // isRecloneAndReplay determines if an update should be handled using reclone-and-replay semantics. @@ -1282,10 +532,15 @@ func isRecloneAndReplay(oldObj, newObj *api.PackageRevision) bool { // recloneAndReplay performs an update by recloning the upstream package and replaying all tasks. // This is more like a git rebase operation than the "classic" kpt update algorithm, which is more like a git merge. -func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo repository.Repository, repositoryObj *configapi.Repository, newObj *api.PackageRevision, packageConfig *builtins.PackageConfig) (repository.PackageRevision, error) { +func (cad *cadEngine) RecloneAndReplay(ctx context.Context, parentPR repository.PackageRevision, version string, repo repository.Repository, repositoryObj *configapi.Repository, newObj *api.PackageRevision) (repository.PackageRevision, error) { ctx, span := tracer.Start(ctx, "cadEngine::recloneAndReplay", trace.WithAttributes()) defer span.End() + packageConfig, err := repository.BuildPackageConfig(ctx, newObj, parentPR) + if err != nil { + return nil, err + } + // For reclone and replay, we create a new package every time // the version should be in newObj so we will overwrite. draft, err := repo.CreatePackageRevision(ctx, newObj) @@ -1293,7 +548,7 @@ func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo return nil, err } - if err := cad.applyTasks(ctx, draft, repositoryObj, newObj, packageConfig); err != nil { + if err := cad.taskHandler.ApplyTasks(ctx, draft, repositoryObj, newObj, packageConfig); err != nil { return nil, err } @@ -1301,32 +556,18 @@ func (cad *cadEngine) recloneAndReplay(ctx context.Context, version string, repo return nil, err } - return draft.Close(ctx, version) -} + repoPkgRev, err := draft.Close(ctx, version) -// ExtractContextConfigMap returns the package-context configmap, if found -func ExtractContextConfigMap(resources map[string]string) (*unstructured.Unstructured, error) { - unstructureds, err := objects.Parser{}.AsUnstructureds(resources) if err != nil { return nil, err } - var matches []*unstructured.Unstructured - for _, o := range unstructureds { - configMapGK := schema.GroupKind{Kind: "ConfigMap"} - if o.GroupVersionKind().GroupKind() == configMapGK { - if o.GetName() == builtins.PkgContextName { - matches = append(matches, o) - } - } - } - if len(matches) == 0 { - return nil, nil - } - - if len(matches) > 1 { - return nil, fmt.Errorf("found multiple configmaps matching name %q", builtins.PkgContextFile) + err = cad.updatePkgRevMeta(ctx, repoPkgRev, newObj) + if err != nil { + return nil, err } - return matches[0], nil + sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Modified, repoPkgRev) + klog.Infof("engine: sent %d for reclone and replay PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName()) + return repoPkgRev, nil } diff --git a/pkg/engine/options.go b/pkg/engine/options.go index 5c25448d..f8b70dec 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -34,6 +34,7 @@ type EngineOptionFunc func(engine *cadEngine) error var _ EngineOption = EngineOptionFunc(nil) func (f EngineOptionFunc) apply(engine *cadEngine) error { + engine.taskHandler.RepoOpener = engine return f(engine) } @@ -47,12 +48,12 @@ func WithCache(cache cache.Cache) EngineOption { func WithBuiltinFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { runtime := newBuiltinRuntime() - if engine.runtime == nil { - engine.runtime = runtime - } else if mr, ok := engine.runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.Runtime == nil { + engine.taskHandler.Runtime = runtime + } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.runtime, runtime}) + engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) } return nil }) @@ -64,12 +65,12 @@ func WithGRPCFunctionRuntime(address string, maxGrpcMessageSize int) EngineOptio if err != nil { return fmt.Errorf("failed to create function runtime: %w", err) } - if engine.runtime == nil { - engine.runtime = runtime - } else if mr, ok := engine.runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.Runtime == nil { + engine.taskHandler.Runtime = runtime + } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.runtime, runtime}) + engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) } return nil }) @@ -77,14 +78,14 @@ func WithGRPCFunctionRuntime(address string, maxGrpcMessageSize int) EngineOptio func WithFunctionRuntime(runtime fn.FunctionRuntime) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runtime = runtime + engine.taskHandler.Runtime = runtime return nil }) } func WithSimpleFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runtime = kpt.NewSimpleFunctionRuntime() + engine.taskHandler.Runtime = kpt.NewSimpleFunctionRuntime() return nil }) } @@ -95,21 +96,21 @@ func WithRunnerOptions(options fnruntime.RunnerOptions) EngineOption { func WithRunnerOptionsResolver(fn func(namespace string) fnruntime.RunnerOptions) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.runnerOptionsResolver = fn + engine.taskHandler.RunnerOptionsResolver = fn return nil }) } func WithCredentialResolver(resolver repository.CredentialResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.credentialResolver = resolver + engine.taskHandler.CredentialResolver = resolver return nil }) } -func WithReferenceResolver(resolver ReferenceResolver) EngineOption { +func WithReferenceResolver(resolver repository.ReferenceResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.referenceResolver = resolver + engine.taskHandler.ReferenceResolver = resolver return nil }) } diff --git a/pkg/engine/package.go b/pkg/engine/package.go deleted file mode 100644 index 95394ee9..00000000 --- a/pkg/engine/package.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 The kpt and Nephio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package engine - -import ( - "context" - "fmt" - - api "github.com/nephio-project/porch/api/porch/v1alpha1" - configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/repository" -) - -type PackageFetcher struct { - repoOpener RepositoryOpener - referenceResolver ReferenceResolver -} - -func (p *PackageFetcher) FetchRevision(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (repository.PackageRevision, error) { - repositoryName, err := parseUpstreamRepository(packageRef.Name) - if err != nil { - return nil, err - } - var resolved configapi.Repository - if err := p.referenceResolver.ResolveReference(ctx, namespace, repositoryName, &resolved); err != nil { - return nil, fmt.Errorf("cannot find repository %s/%s: %w", namespace, repositoryName, err) - } - - repo, err := p.repoOpener.OpenRepository(ctx, &resolved) - if err != nil { - return nil, err - } - - revisions, err := repo.ListPackageRevisions(ctx, repository.ListPackageRevisionFilter{KubeObjectName: packageRef.Name}) - if err != nil { - return nil, err - } - - var revision repository.PackageRevision - for _, rev := range revisions { - if rev.KubeObjectName() == packageRef.Name { - revision = rev - break - } - } - if revision == nil { - return nil, fmt.Errorf("cannot find package revision %q", packageRef.Name) - } - - return revision, nil -} - -func (p *PackageFetcher) FetchResources(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (*api.PackageRevisionResources, error) { - revision, err := p.FetchRevision(ctx, packageRef, namespace) - if err != nil { - return nil, err - } - - resources, err := revision.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot read contents of package %q: %w", packageRef.Name, err) - } - return resources, nil -} diff --git a/pkg/engine/testdata/clone/bucket/Kptfile b/pkg/engine/testdata/clone/bucket/Kptfile deleted file mode 100644 index e2b651e4..00000000 --- a/pkg/engine/testdata/clone/bucket/Kptfile +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: bucket -info: - description: Bucket test package diff --git a/pkg/engine/testdata/clone/bucket/bucket.yaml b/pkg/engine/testdata/clone/bucket/bucket.yaml deleted file mode 100644 index 9b75ffcb..00000000 --- a/pkg/engine/testdata/clone/bucket/bucket.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/clone/configmap/Kptfile b/pkg/engine/testdata/clone/configmap/Kptfile deleted file mode 100644 index 54fdea56..00000000 --- a/pkg/engine/testdata/clone/configmap/Kptfile +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: configmap -info: - description: ConfigMap test package diff --git a/pkg/engine/testdata/clone/configmap/configmap.yaml b/pkg/engine/testdata/clone/configmap/configmap.yaml deleted file mode 100644 index 8b4c9896..00000000 --- a/pkg/engine/testdata/clone/configmap/configmap.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: configmap-name - namespace: configmap-namespace -data: - key: value diff --git a/pkg/engine/testdata/context/expected/Kptfile b/pkg/engine/testdata/context/expected/Kptfile deleted file mode 100644 index 1e92a6cf..00000000 --- a/pkg/engine/testdata/context/expected/Kptfile +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: input -info: - description: Builtin Function Test Package diff --git a/pkg/engine/testdata/context/expected/bucket.yaml b/pkg/engine/testdata/context/expected/bucket.yaml deleted file mode 100644 index 5dad26d4..00000000 --- a/pkg/engine/testdata/context/expected/bucket.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/context/expected/package-context.yaml b/pkg/engine/testdata/context/expected/package-context.yaml deleted file mode 100644 index 57b4e523..00000000 --- a/pkg/engine/testdata/context/expected/package-context.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: input - package-path: parent1/parent1.2/parent1.2.3/me diff --git a/pkg/engine/testdata/context/input/Kptfile b/pkg/engine/testdata/context/input/Kptfile deleted file mode 100644 index 1e92a6cf..00000000 --- a/pkg/engine/testdata/context/input/Kptfile +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: input -info: - description: Builtin Function Test Package diff --git a/pkg/engine/testdata/context/input/bucket.yaml b/pkg/engine/testdata/context/input/bucket.yaml deleted file mode 100644 index 5dad26d4..00000000 --- a/pkg/engine/testdata/context/input/bucket.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: - name: bucket-name - namespace: bucket-namespace - annotations: - cnrm.cloud.google.com/project-id: bucket-project -spec: - storageClass: standard - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/init/testpkg/Kptfile b/pkg/engine/testdata/init/testpkg/Kptfile deleted file mode 100644 index 7883a649..00000000 --- a/pkg/engine/testdata/init/testpkg/Kptfile +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: testpkg - annotations: - config.kubernetes.io/local-config: "true" -info: - site: http://kpt.dev/testpkg - description: test package - keywords: - - test - - kpt - - pkg diff --git a/pkg/engine/testdata/init/testpkg/README.md b/pkg/engine/testdata/init/testpkg/README.md deleted file mode 100644 index 9a34a509..00000000 --- a/pkg/engine/testdata/init/testpkg/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# testpkg - -## Description -test package - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] testpkg` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree testpkg` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init testpkg -kpt live apply testpkg --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/init/testpkg/package-context.yaml b/pkg/engine/testdata/init/testpkg/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/init/testpkg/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/replace/Kptfile b/pkg/engine/testdata/replace/Kptfile deleted file mode 100644 index 9529df9a..00000000 --- a/pkg/engine/testdata/replace/Kptfile +++ /dev/null @@ -1,23 +0,0 @@ -# top comment -apiVersion: kpt.dev/v1 -# Kptfile info -info: - # Kptfile description - description: A Google Cloud Storage bucket -# Kptfile kind -kind: Kptfile -# Kptfile metadata -metadata: - annotations: - blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint - name: simple-bucket -# Kptfile pipeline -pipeline: - # Kptfile mutators - mutators: - - configMap: - name: updated-bucket-name - namespace: updated-namespace - project-id: updated-project-id - storage-class: updated-storage-class - image: gcr.io/kpt-fn/apply-setters:v0.2.0 diff --git a/pkg/engine/testdata/replace/README.md b/pkg/engine/testdata/replace/README.md deleted file mode 100644 index 6f0623fa..00000000 --- a/pkg/engine/testdata/replace/README.md +++ /dev/null @@ -1 +0,0 @@ -# replace test \ No newline at end of file diff --git a/pkg/engine/testdata/replace/bucket.yaml b/pkg/engine/testdata/replace/bucket.yaml deleted file mode 100644 index 935e6573..00000000 --- a/pkg/engine/testdata/replace/bucket.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# top comment -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -# metadata comment -metadata: # kpt-merge: config-control/blueprints-project-bucket - # annotations comment - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} - name: blueprints-project-bucket # kpt-set: ${project-id}-${name} - namespace: config-control # kpt-set: ${namespace} -# spec comment -spec: - storageClass: standard # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - # Versioning is enabled - versioning: - enabled: false diff --git a/pkg/engine/testdata/simple-render/expected.txt b/pkg/engine/testdata/simple-render/expected.txt deleted file mode 100644 index f625b40c..00000000 --- a/pkg/engine/testdata/simple-render/expected.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: # kpt-merge: config-control/blueprints-project-bucket - name: updated-project-id-updated-bucket-name # kpt-set: ${project-id}-${name} - namespace: updated-namespace # kpt-set: ${namespace} - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: updated-project-id # kpt-set: ${project-id} - cnrm.cloud.google.com/blueprint: 'kpt-fn' -spec: - storageClass: updated-storage-class # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/simple-render/simple-bucket/Kptfile b/pkg/engine/testdata/simple-render/simple-bucket/Kptfile deleted file mode 100644 index 7a6d5063..00000000 --- a/pkg/engine/testdata/simple-render/simple-bucket/Kptfile +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: simple-bucket - annotations: - blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint -info: - description: A Google Cloud Storage bucket -pipeline: - mutators: - - image: gcr.io/kpt-fn/apply-setters:v0.2.0 - configMap: - name: updated-bucket-name - namespace: updated-namespace - project-id: updated-project-id - storage-class: updated-storage-class diff --git a/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml b/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml deleted file mode 100644 index f80f88b6..00000000 --- a/pkg/engine/testdata/simple-render/simple-bucket/bucket.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2022 The kpt and Nephio Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -apiVersion: storage.cnrm.cloud.google.com/v1beta1 -kind: StorageBucket -metadata: # kpt-merge: config-control/blueprints-project-bucket - name: blueprints-project-bucket # kpt-set: ${project-id}-${name} - namespace: config-control # kpt-set: ${namespace} - annotations: - cnrm.cloud.google.com/force-destroy: "false" - cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} -spec: - storageClass: standard # kpt-set: ${storage-class} - uniformBucketLevelAccess: true - versioning: - enabled: false diff --git a/pkg/engine/testdata/update/local/Kptfile b/pkg/engine/testdata/update/local/Kptfile deleted file mode 100644 index 56adbced..00000000 --- a/pkg/engine/testdata/update/local/Kptfile +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: backend - annotations: - config.kubernetes.io/local-config: "true" -upstream: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 -upstreamLock: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 - commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/local/README.md b/pkg/engine/testdata/update/local/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/local/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/local/namespace.yaml b/pkg/engine/testdata/update/local/namespace.yaml deleted file mode 100644 index 5a044679..00000000 --- a/pkg/engine/testdata/update/local/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: # kpt-merge: example - name: backend -spec: {} diff --git a/pkg/engine/testdata/update/local/package-context.yaml b/pkg/engine/testdata/update/local/package-context.yaml deleted file mode 100644 index 9b77f45e..00000000 --- a/pkg/engine/testdata/update/local/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: backend diff --git a/pkg/engine/testdata/update/local/resourcequota.yaml b/pkg/engine/testdata/update/local/resourcequota.yaml deleted file mode 100644 index 02e22b08..00000000 --- a/pkg/engine/testdata/update/local/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: # kpt-merge: example/default - name: default - namespace: backend -spec: - hard: - cpu: "40" - memory: 40G diff --git a/pkg/engine/testdata/update/local/rolebinding.yaml b/pkg/engine/testdata/update/local/rolebinding.yaml deleted file mode 100644 index f57f1f2e..00000000 --- a/pkg/engine/testdata/update/local/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: # kpt-merge: example/app-admin - name: app-admin - namespace: backend -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: backend.admin@bigco.com diff --git a/pkg/engine/testdata/update/local/update-rolebinding.yaml b/pkg/engine/testdata/update/local/update-rolebinding.yaml deleted file mode 100644 index 6fdff582..00000000 --- a/pkg/engine/testdata/update/local/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: # kpt-merge: example/update-rolebinding - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/original/Kptfile b/pkg/engine/testdata/update/original/Kptfile deleted file mode 100644 index db627135..00000000 --- a/pkg/engine/testdata/update/original/Kptfile +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: basens - annotations: - config.kubernetes.io/local-config: "true" -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/original/README.md b/pkg/engine/testdata/update/original/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/original/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/original/namespace.yaml b/pkg/engine/testdata/update/original/namespace.yaml deleted file mode 100644 index 9219ae32..00000000 --- a/pkg/engine/testdata/update/original/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: example -spec: {} diff --git a/pkg/engine/testdata/update/original/package-context.yaml b/pkg/engine/testdata/update/original/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/update/original/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/update/original/resourcequota.yaml b/pkg/engine/testdata/update/original/resourcequota.yaml deleted file mode 100644 index a5c7f9dc..00000000 --- a/pkg/engine/testdata/update/original/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: - name: default - namespace: example -spec: - hard: - cpu: "40" - memory: 40G diff --git a/pkg/engine/testdata/update/original/rolebinding.yaml b/pkg/engine/testdata/update/original/rolebinding.yaml deleted file mode 100644 index 12ce8875..00000000 --- a/pkg/engine/testdata/update/original/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: app-admin - namespace: example -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: example.admin@bigco.com diff --git a/pkg/engine/testdata/update/original/update-rolebinding.yaml b/pkg/engine/testdata/update/original/update-rolebinding.yaml deleted file mode 100644 index a20edf50..00000000 --- a/pkg/engine/testdata/update/original/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/updated/Kptfile b/pkg/engine/testdata/update/updated/Kptfile deleted file mode 100644 index 56adbced..00000000 --- a/pkg/engine/testdata/update/updated/Kptfile +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: backend - annotations: - config.kubernetes.io/local-config: "true" -upstream: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 -upstreamLock: - type: git - git: - repo: https://github.com/droot/pkg-catalog.git - directory: basens - ref: basens/v0 - commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/updated/README.md b/pkg/engine/testdata/update/updated/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/updated/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/updated/namespace.yaml b/pkg/engine/testdata/update/updated/namespace.yaml deleted file mode 100644 index 5a044679..00000000 --- a/pkg/engine/testdata/update/updated/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: # kpt-merge: example - name: backend -spec: {} diff --git a/pkg/engine/testdata/update/updated/package-context.yaml b/pkg/engine/testdata/update/updated/package-context.yaml deleted file mode 100644 index 9b77f45e..00000000 --- a/pkg/engine/testdata/update/updated/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: backend diff --git a/pkg/engine/testdata/update/updated/resourcequota.yaml b/pkg/engine/testdata/update/updated/resourcequota.yaml deleted file mode 100644 index e7bc953e..00000000 --- a/pkg/engine/testdata/update/updated/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: # kpt-merge: example/default - name: default - namespace: backend -spec: - hard: - cpu: "40" - memory: 60G diff --git a/pkg/engine/testdata/update/updated/rolebinding.yaml b/pkg/engine/testdata/update/updated/rolebinding.yaml deleted file mode 100644 index f57f1f2e..00000000 --- a/pkg/engine/testdata/update/updated/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: # kpt-merge: example/app-admin - name: app-admin - namespace: backend -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: backend.admin@bigco.com diff --git a/pkg/engine/testdata/update/updated/update-rolebinding.yaml b/pkg/engine/testdata/update/updated/update-rolebinding.yaml deleted file mode 100644 index 6fdff582..00000000 --- a/pkg/engine/testdata/update/updated/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: # kpt-merge: example/update-rolebinding - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/testdata/update/upstream/Kptfile b/pkg/engine/testdata/update/upstream/Kptfile deleted file mode 100644 index 65fe6a65..00000000 --- a/pkg/engine/testdata/update/upstream/Kptfile +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kpt.dev/v1 -kind: Kptfile -metadata: - name: basens - annotations: - config.kubernetes.io/local-config: "true" -info: - description: kpt package for provisioning namespace -pipeline: - mutators: - - image: gcr.io/kpt-fn/set-namespace:v0.3.4 - configPath: package-context.yaml - - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 - configPath: update-rolebinding.yaml diff --git a/pkg/engine/testdata/update/upstream/README.md b/pkg/engine/testdata/update/upstream/README.md deleted file mode 100644 index 2b9db73e..00000000 --- a/pkg/engine/testdata/update/upstream/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# basens - -## Description -kpt package for provisioning namespace - -## Usage - -### Fetch the package -`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` -Details: https://kpt.dev/reference/cli/pkg/get/ - -### View package content -`kpt pkg tree basens` -Details: https://kpt.dev/reference/cli/pkg/tree/ - -### Apply the package -``` -kpt live init basens -kpt live apply basens --reconcile-timeout=2m --output=table -``` -Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/engine/testdata/update/upstream/namespace.yaml b/pkg/engine/testdata/update/upstream/namespace.yaml deleted file mode 100644 index 9219ae32..00000000 --- a/pkg/engine/testdata/update/upstream/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: example -spec: {} diff --git a/pkg/engine/testdata/update/upstream/package-context.yaml b/pkg/engine/testdata/update/upstream/package-context.yaml deleted file mode 100644 index 6c33d9e4..00000000 --- a/pkg/engine/testdata/update/upstream/package-context.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: kptfile.kpt.dev - annotations: - config.kubernetes.io/local-config: "true" -data: - name: example diff --git a/pkg/engine/testdata/update/upstream/resourcequota.yaml b/pkg/engine/testdata/update/upstream/resourcequota.yaml deleted file mode 100644 index 5a56078d..00000000 --- a/pkg/engine/testdata/update/upstream/resourcequota.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ResourceQuota -metadata: - name: default - namespace: example -spec: - hard: - cpu: "40" - memory: 60G diff --git a/pkg/engine/testdata/update/upstream/rolebinding.yaml b/pkg/engine/testdata/update/upstream/rolebinding.yaml deleted file mode 100644 index 12ce8875..00000000 --- a/pkg/engine/testdata/update/upstream/rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: app-admin - namespace: example -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: app-admin -subjects: -- apiGroup: rbac.authorization.k8s.io - kind: Group - name: example.admin@bigco.com diff --git a/pkg/engine/testdata/update/upstream/update-rolebinding.yaml b/pkg/engine/testdata/update/upstream/update-rolebinding.yaml deleted file mode 100644 index a20edf50..00000000 --- a/pkg/engine/testdata/update/upstream/update-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: fn.kpt.dev/v1alpha1 -kind: ApplyReplacements -metadata: - name: update-rolebinding - annotations: - config.kubernetes.io/local-config: "true" -replacements: -- source: - kind: ConfigMap - name: kptfile.kpt.dev - fieldPath: data.name - targets: - - select: - name: app-admin - kind: RoleBinding - fieldPaths: - - subjects.[kind=Group].name - options: - delimiter: '.' - index: 0 diff --git a/pkg/engine/watchermanager.go b/pkg/engine/watchermanager.go index 5a2d74eb..b64c30be 100644 --- a/pkg/engine/watchermanager.go +++ b/pkg/engine/watchermanager.go @@ -18,7 +18,6 @@ import ( "context" "sync" - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/watch" "k8s.io/klog/v2" @@ -31,7 +30,7 @@ type WatcherManager interface { // PackageRevisionWatcher is the callback interface for watchers. type ObjectWatcher interface { - OnPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) bool + OnPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) bool } func NewWatcherManager() *watcherManager { @@ -95,7 +94,7 @@ func (r *watcherManager) WatchPackageRevisions(ctx context.Context, filter repos } // notifyPackageRevisionChange is called to send a change notification to all interested listeners. -func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int { +func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision) int { r.mutex.Lock() defer r.mutex.Unlock() @@ -109,7 +108,7 @@ func (r *watcherManager) NotifyPackageRevisionChange(eventType watch.EventType, r.watchers[i] = nil continue } - if keepGoing := watcher.callback.OnPackageRevisionChange(eventType, obj, objMeta); !keepGoing { + if keepGoing := watcher.callback.OnPackageRevisionChange(eventType, obj); !keepGoing { klog.Infof("stopping watcher in response to !keepGoing") r.watchers[i] = nil } diff --git a/pkg/git/git.go b/pkg/git/git.go index 4794acba..bca58d2a 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1107,7 +1107,7 @@ func (r *gitRepository) pushAndCleanup(ctx context.Context, ph *pushRefSpecBuild return nil } -func (r *gitRepository) loadTasks(ctx context.Context, startCommit *object.Commit, packagePath string, +func (r *gitRepository) loadTasks(_ context.Context, startCommit *object.Commit, packagePath string, workspaceName v1alpha1.WorkspaceName) ([]v1alpha1.Task, error) { var logOptions = git.LogOptions{ @@ -1709,6 +1709,10 @@ func (r *gitRepository) discoverPackagesInTree(commit *object.Commit, opt Discov return t, nil } +func (r *gitRepository) Refresh(_ context.Context) error { + return nil +} + // See https://eli.thegreenplace.net/2021/generic-functions-on-slices-with-go-type-parameters/ // func ReverseSlice[T any](s []T) { // Ready for generics! func reverseSlice(s []v1alpha1.Task) { diff --git a/pkg/git/package.go b/pkg/git/package.go index c39962d6..b3c2ff26 100644 --- a/pkg/git/package.go +++ b/pkg/git/package.go @@ -29,6 +29,7 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/internal/kpt/pkg" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -50,6 +51,7 @@ type gitPackageRevision struct { tree plumbing.Hash // Cached tree of the package itself, some descendent of commit.Tree() commit plumbing.Hash // Current version of the package (commit sha) tasks []v1alpha1.Task + metadata meta.PackageRevisionMeta } var _ repository.PackageRevision = &gitPackageRevision{} @@ -316,4 +318,12 @@ func (p *gitPackageRevision) UpdateLifecycle(ctx context.Context, new v1alpha1.P return p.repo.UpdateLifecycle(ctx, p, new) } +func (p *gitPackageRevision) GetMeta() meta.PackageRevisionMeta { + return p.metadata +} + +func (p *gitPackageRevision) SetMeta(metadata meta.PackageRevisionMeta) { + p.metadata = metadata +} + // TODO: Define a type `gitPackage` to implement the Repository.Package interface diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index e43157fe..4b683ac8 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -31,6 +31,7 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/internal/kpt/pkg" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -244,6 +245,10 @@ func (r *ociRepository) buildPackageRevision(ctx context.Context, name oci.Image return p, nil } +func (r *ociRepository) Refresh(_ context.Context) error { + return nil +} + // ToMainPackageRevision implements repository.PackageRevision. func (p *ociPackageRevision) ToMainPackageRevision() repository.PackageRevision { panic("unimplemented") @@ -257,6 +262,7 @@ type ociPackageRevision struct { created time.Time resourceVersion string uid types.UID + metadata meta.PackageRevisionMeta parent *ociRepository tasks []v1alpha1.Task @@ -418,3 +424,11 @@ func (p *ociPackageRevision) UpdateLifecycle(ctx context.Context, new v1alpha1.P } return nil } + +func (p *ociPackageRevision) GetMeta() meta.PackageRevisionMeta { + return p.metadata +} + +func (p *ociPackageRevision) SetMeta(metadata meta.PackageRevisionMeta) { + p.metadata = metadata +} diff --git a/pkg/registry/porch/package.go b/pkg/registry/porch/package.go index 87978404..e5d8414f 100644 --- a/pkg/registry/porch/package.go +++ b/pkg/registry/porch/package.go @@ -19,7 +19,7 @@ import ( "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -46,7 +46,7 @@ var _ rest.GracefulDeleter = &packages{} var _ rest.SingularNameProvider = &packages{} // GetSingularName implements the SingularNameProvider interface -func (r *packages) GetSingularName() (string) { +func (r *packages) GetSingularName() string { return "package" } @@ -81,7 +81,7 @@ func (r *packages) List(ctx context.Context, options *metainternalversion.ListOp return nil, err } - if err := r.packageCommon.listPackages(ctx, filter, func(p *engine.Package) error { + if err := r.packageCommon.listPackages(ctx, filter, func(p repository.Package) error { item := p.GetPackage() result.Items = append(result.Items, *item) return nil @@ -157,7 +157,7 @@ func (r *packages) Create(ctx context.Context, runtimeObject runtime.Object, cre // Update finds a resource in the storage and updates it. Some implementations // may allow updates creates the object - they should set the created boolean // to true. -func (r *packages) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, +func (r *packages) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { ctx, span := tracer.Start(ctx, "packages::Update", trace.WithAttributes()) defer span.End() diff --git a/pkg/registry/porch/packagecommon.go b/pkg/registry/porch/packagecommon.go index cb2f66af..74ddb44b 100644 --- a/pkg/registry/porch/packagecommon.go +++ b/pkg/registry/porch/packagecommon.go @@ -24,6 +24,7 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/pkg/engine" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/util" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -55,7 +56,7 @@ type packageCommon struct { } func (r *packageCommon) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, - selector labels.Selector, callback func(p *engine.PackageRevision) error) error { + selector labels.Selector, callback func(p repository.PackageRevision) error) error { var opts []client.ListOption ns, namespaced := genericapirequest.NamespaceFrom(ctx) if namespaced && ns != "" { @@ -102,7 +103,7 @@ func (r *packageCommon) listPackageRevisions(ctx context.Context, filter package return nil } -func (r *packageCommon) listPackages(ctx context.Context, filter packageFilter, callback func(p *engine.Package) error) error { +func (r *packageCommon) listPackages(ctx context.Context, filter packageFilter, callback func(p repository.Package) error) error { var opts []client.ListOption if ns, namespaced := genericapirequest.NamespaceFrom(ctx); namespaced { opts = append(opts, client.InNamespace(ns)) @@ -152,7 +153,7 @@ func (r *packageCommon) getRepositoryObjFromName(ctx context.Context, name strin if !namespaced { return nil, fmt.Errorf("namespace must be specified") } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, apierrors.NewNotFound(r.gr, name) } @@ -171,7 +172,7 @@ func (r *packageCommon) getRepositoryObj(ctx context.Context, repositoryID types return &repositoryObj, nil } -func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (*engine.PackageRevision, error) { +func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (repository.PackageRevision, error) { repositoryObj, err := r.getRepositoryObjFromName(ctx, name) if err != nil { return nil, err @@ -189,7 +190,7 @@ func (r *packageCommon) getRepoPkgRev(ctx context.Context, name string) (*engine return nil, apierrors.NewNotFound(r.gr, name) } -func (r *packageCommon) getPackage(ctx context.Context, name string) (*engine.Package, error) { +func (r *packageCommon) getPackage(ctx context.Context, name string) (repository.Package, error) { repositoryObj, err := r.getRepositoryObjFromName(ctx, name) if err != nil { return nil, err @@ -279,7 +280,7 @@ func (r *packageCommon) updatePackageRevision(ctx context.Context, name string, return nil, false, apierrors.NewBadRequest(fmt.Sprintf("expected PackageRevision object, got %T", newRuntimeObj)) } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } @@ -299,7 +300,7 @@ func (r *packageCommon) updatePackageRevision(ctx context.Context, name string, return nil, false, apierrors.NewInternalError(fmt.Errorf("error getting repository %v: %w", repositoryID, err)) } - var parentPackage *engine.PackageRevision + var parentPackage repository.PackageRevision if newApiPkgRev.Spec.Parent != nil && newApiPkgRev.Spec.Parent.Name != "" { p, err := r.getRepoPkgRev(ctx, newApiPkgRev.Spec.Parent.Name) if err != nil { @@ -391,7 +392,7 @@ func (r *packageCommon) updatePackage(ctx context.Context, name string, objInfo return nil, false, apierrors.NewBadRequest(fmt.Sprintf("expected Package object, got %T", newRuntimeObj)) } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } @@ -441,7 +442,7 @@ func (r *packageCommon) validateDelete(ctx context.Context, deleteValidation res return nil, err } } - repositoryName, err := ParseRepositoryName(repoName) + repositoryName, err := util.ParseRepositoryName(repoName) if err != nil { return nil, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", repoName)) } diff --git a/pkg/registry/porch/packagerevision.go b/pkg/registry/porch/packagerevision.go index dc4e7cbd..357d1ae7 100644 --- a/pkg/registry/porch/packagerevision.go +++ b/pkg/registry/porch/packagerevision.go @@ -19,7 +19,7 @@ import ( "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -85,7 +85,7 @@ func (r *packageRevisions) List(ctx context.Context, options *metainternalversio return nil, err } - if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p *engine.PackageRevision) error { + if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p repository.PackageRevision) error { item, err := p.GetPackageRevision(ctx) if err != nil { return err @@ -155,7 +155,7 @@ func (r *packageRevisions) Create(ctx context.Context, runtimeObject runtime.Obj return nil, apierrors.NewInvalid(api.SchemeGroupVersion.WithKind("PackageRevision").GroupKind(), newApiPkgRev.Name, fieldErrors) } - var parentPackage *engine.PackageRevision + var parentPackage repository.PackageRevision if newApiPkgRev.Spec.Parent != nil && newApiPkgRev.Spec.Parent.Name != "" { p, err := r.packageCommon.getRepoPkgRev(ctx, newApiPkgRev.Spec.Parent.Name) if err != nil { diff --git a/pkg/registry/porch/packagerevisionresources.go b/pkg/registry/porch/packagerevisionresources.go index 4f71a443..bd6fc6fc 100644 --- a/pkg/registry/porch/packagerevisionresources.go +++ b/pkg/registry/porch/packagerevisionresources.go @@ -22,7 +22,8 @@ import ( api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/util" "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -82,7 +83,7 @@ func (r *packageRevisionResources) List(ctx context.Context, options *metaintern return nil, err } - if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p *engine.PackageRevision) error { + if err := r.packageCommon.listPackageRevisions(ctx, filter, options.LabelSelector, func(p repository.PackageRevision) error { apiPkgResources, err := p.GetResources(ctx) if err != nil { return err @@ -166,7 +167,7 @@ func (r *packageRevisionResources) Update(ctx context.Context, name string, objI } } - repositoryName, err := ParseRepositoryName(name) + repositoryName, err := util.ParseRepositoryName(name) if err != nil { return nil, false, apierrors.NewBadRequest(fmt.Sprintf("invalid name %q", name)) } diff --git a/pkg/registry/porch/reference.go b/pkg/registry/porch/reference.go index 8c4b64ee..5fce1ab9 100644 --- a/pkg/registry/porch/reference.go +++ b/pkg/registry/porch/reference.go @@ -17,11 +17,11 @@ package porch import ( "context" - "github.com/nephio-project/porch/pkg/engine" + "github.com/nephio-project/porch/pkg/repository" "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewReferenceResolver(coreClient client.Reader) engine.ReferenceResolver { +func NewReferenceResolver(coreClient client.Reader) repository.ReferenceResolver { return &referenceResolver{ coreClient: coreClient, } @@ -31,9 +31,9 @@ type referenceResolver struct { coreClient client.Reader } -var _ engine.ReferenceResolver = &referenceResolver{} +var _ repository.ReferenceResolver = &referenceResolver{} -func (r *referenceResolver) ResolveReference(ctx context.Context, namespace, name string, result engine.Object) error { +func (r *referenceResolver) ResolveReference(ctx context.Context, namespace, name string, result repository.Object) error { return r.coreClient.Get(ctx, client.ObjectKey{ Namespace: namespace, Name: name, diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index f3927885..3b721046 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -20,7 +20,6 @@ import ( "sync" "github.com/nephio-project/porch/pkg/engine" - "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel/trace" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -72,7 +71,7 @@ type watcher struct { // mutex that protects the eventCallback and done fields // from concurrent access. mutex sync.Mutex - eventCallback func(eventType watch.EventType, pr engine.PackageRevision) bool + eventCallback func(eventType watch.EventType, pr repository.PackageRevision) bool done bool totalSent int } @@ -94,7 +93,7 @@ func (w *watcher) ResultChan() <-chan watch.Event { type packageReader interface { watchPackages(ctx context.Context, filter packageRevisionFilter, callback engine.ObjectWatcher) error - listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p *engine.PackageRevision) error) error + listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p repository.PackageRevision) error) error } // listAndWatch implements watch by doing a list, then sending any observed changes. @@ -120,7 +119,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter // Make sure we hold the lock when setting the eventCallback, as it // will be read by other goroutines when events happen. w.mutex.Lock() - w.eventCallback = func(eventType watch.EventType, pr engine.PackageRevision) bool { + w.eventCallback = func(eventType watch.EventType, pr repository.PackageRevision) bool { if w.done { return false } @@ -146,7 +145,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter sentAdd := 0 // TODO: Only if rv == 0? - if err := r.listPackageRevisions(ctx, filter, selector, func(p *engine.PackageRevision) error { + if err := r.listPackageRevisions(ctx, filter, selector, func(p repository.PackageRevision) error { obj, err := p.GetPackageRevision(ctx) if err != nil { w.mutex.Lock() @@ -199,7 +198,7 @@ func (w *watcher) listAndWatchInner(ctx context.Context, r packageReader, filter } klog.Infof("watch %p: moving watch into streaming mode after sentAdd %d, sentBacklog %d, sentNewBacklog %d", w, sentAdd, sentBacklog, sentNewBacklog) - w.eventCallback = func(eventType watch.EventType, pr engine.PackageRevision) bool { + w.eventCallback = func(eventType watch.EventType, pr repository.PackageRevision) bool { if w.done { return false } @@ -245,9 +244,9 @@ func (w *watcher) sendWatchEvent(ev watch.Event) { } // OnPackageRevisionChange is the callback called when a PackageRevision changes. -func (w *watcher) OnPackageRevisionChange(eventType watch.EventType, pr repository.PackageRevision, objMeta meta.PackageRevisionMeta) bool { +func (w *watcher) OnPackageRevisionChange(eventType watch.EventType, pr repository.PackageRevision) bool { w.mutex.Lock() defer w.mutex.Unlock() - return w.eventCallback(eventType, *engine.ToPackageRevision(pr, objMeta)) + return w.eventCallback(eventType, pr) } diff --git a/pkg/registry/porch/watch_test.go b/pkg/registry/porch/watch_test.go index 3a32a788..bbf91d91 100644 --- a/pkg/registry/porch/watch_test.go +++ b/pkg/registry/porch/watch_test.go @@ -22,8 +22,8 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/pkg/engine" - "github.com/nephio-project/porch/pkg/engine/fake" - "github.com/nephio-project/porch/pkg/meta" + "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -64,14 +64,14 @@ func TestWatcherClose(t *testing.T) { for { select { case <-ch: - pkgRev := &fake.PackageRevision{ + pkgRev := &fake.FakePackageRevision{ PackageRevision: &v1alpha1.PackageRevision{ ObjectMeta: metav1.ObjectMeta{ Labels: make(map[string]string), }, }, } - if cont := r.callback.OnPackageRevisionChange(watch.Modified, pkgRev, meta.PackageRevisionMeta{}); !cont { + if cont := r.callback.OnPackageRevisionChange(watch.Modified, pkgRev); !cont { return } case <-timer.C: @@ -97,6 +97,6 @@ func (f *fakePackageReader) watchPackages(ctx context.Context, filter packageRev return nil } -func (f *fakePackageReader) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p *engine.PackageRevision) error) error { +func (f *fakePackageReader) listPackageRevisions(ctx context.Context, filter packageRevisionFilter, selector labels.Selector, callback func(p repository.PackageRevision) error) error { return nil } diff --git a/pkg/engine/environment.go b/pkg/repository/environment.go similarity index 80% rename from pkg/engine/environment.go rename to pkg/repository/environment.go index 7cb99378..96d542dc 100644 --- a/pkg/engine/environment.go +++ b/pkg/repository/environment.go @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -26,6 +27,10 @@ type Object interface { runtime.Object } +type RepositoryOpener interface { + OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (Repository, error) +} + type ReferenceResolver interface { ResolveReference(ctx context.Context, namespace, name string, result Object) error } diff --git a/pkg/engine/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go similarity index 53% rename from pkg/engine/fake/packagerevision.go rename to pkg/repository/fake/packagerevision.go index 713c0b99..6fb1fcba 100644 --- a/pkg/engine/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -19,12 +19,13 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" "k8s.io/apimachinery/pkg/types" ) // Implementation of the repository.PackageRevision interface for testing. -type PackageRevision struct { +type FakePackageRevision struct { Name string Namespace string Uid types.UID @@ -35,57 +36,71 @@ type PackageRevision struct { Kptfile kptfile.KptFile } -func (pr *PackageRevision) KubeObjectName() string { +func (pr *FakePackageRevision) KubeObjectName() string { return pr.Name } -var _ repository.PackageRevision = &PackageRevision{} +var _ repository.PackageRevision = &FakePackageRevision{} // ToMainPackageRevision implements repository.PackageRevision. -func (f *PackageRevision) ToMainPackageRevision() repository.PackageRevision { +func (f *FakePackageRevision) ToMainPackageRevision() repository.PackageRevision { panic("unimplemented") } -func (pr *PackageRevision) KubeObjectNamespace() string { +func (pr *FakePackageRevision) KubeObjectNamespace() string { return pr.Namespace } -func (pr *PackageRevision) UID() types.UID { +func (pr *FakePackageRevision) UID() types.UID { return pr.Uid } -func (pr *PackageRevision) ResourceVersion() string { +func (pr *FakePackageRevision) ResourceVersion() string { return pr.PackageRevision.ResourceVersion } -func (pr *PackageRevision) Key() repository.PackageRevisionKey { +func (pr *FakePackageRevision) Key() repository.PackageRevisionKey { return pr.PackageRevisionKey } -func (pr *PackageRevision) Lifecycle() v1alpha1.PackageRevisionLifecycle { +func (pr *FakePackageRevision) Lifecycle() v1alpha1.PackageRevisionLifecycle { return pr.PackageLifecycle } -func (pr *PackageRevision) GetPackageRevision(context.Context) (*v1alpha1.PackageRevision, error) { +func (pr *FakePackageRevision) GetPackageRevision(context.Context) (*v1alpha1.PackageRevision, error) { return pr.PackageRevision, nil } -func (f *PackageRevision) GetResources(context.Context) (*v1alpha1.PackageRevisionResources, error) { +func (f *FakePackageRevision) GetResources(context.Context) (*v1alpha1.PackageRevisionResources, error) { return f.Resources, nil } -func (f *PackageRevision) GetKptfile(ctx context.Context) (kptfile.KptFile, error) { +func (f *FakePackageRevision) GetKptfile(ctx context.Context) (kptfile.KptFile, error) { return f.Kptfile, nil } -func (f *PackageRevision) GetUpstreamLock(context.Context) (kptfile.Upstream, kptfile.UpstreamLock, error) { +func (f *FakePackageRevision) GetUpstreamLock(context.Context) (kptfile.Upstream, kptfile.UpstreamLock, error) { return *f.Kptfile.Upstream, *f.Kptfile.UpstreamLock, nil } -func (f *PackageRevision) GetLock() (kptfile.Upstream, kptfile.UpstreamLock, error) { +func (f *FakePackageRevision) GetLock() (kptfile.Upstream, kptfile.UpstreamLock, error) { return *f.Kptfile.Upstream, *f.Kptfile.UpstreamLock, nil } -func (f *PackageRevision) UpdateLifecycle(context.Context, v1alpha1.PackageRevisionLifecycle) error { +func (f *FakePackageRevision) UpdateLifecycle(context.Context, v1alpha1.PackageRevisionLifecycle) error { return nil } + +func (f *FakePackageRevision) GetMeta() meta.PackageRevisionMeta { + return meta.PackageRevisionMeta{} +} + +func (f *FakePackageRevision) SetMeta(meta.PackageRevisionMeta) { +} + +func (f *FakePackageRevision) IsLatest() bool { + return false +} + +func (f *FakePackageRevision) SetLatest(latest bool) { +} diff --git a/pkg/engine/fake/repository.go b/pkg/repository/fake/repository.go similarity index 97% rename from pkg/engine/fake/repository.go rename to pkg/repository/fake/repository.go index be01a388..ff9abd72 100644 --- a/pkg/engine/fake/repository.go +++ b/pkg/repository/fake/repository.go @@ -80,3 +80,7 @@ func (r *Repository) CreatePackage(_ context.Context, pr *v1alpha1.PorchPackage) func (r *Repository) DeletePackage(_ context.Context, pr repository.Package) error { return nil } + +func (r *Repository) Refresh(_ context.Context) error { + return nil +} diff --git a/pkg/repository/package.go b/pkg/repository/package.go new file mode 100644 index 00000000..77836afd --- /dev/null +++ b/pkg/repository/package.go @@ -0,0 +1,153 @@ +// Copyright 2022 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package repository + +import ( + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/pkg/objects" + "github.com/nephio-project/porch/pkg/util" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type PackageFetcher struct { + RepoOpener RepositoryOpener + ReferenceResolver ReferenceResolver +} + +func (p *PackageFetcher) FetchRevision(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (PackageRevision, error) { + repositoryName, err := util.ParseRepositoryName(packageRef.Name) + if err != nil { + return nil, err + } + var resolved configapi.Repository + if err := p.ReferenceResolver.ResolveReference(ctx, namespace, repositoryName, &resolved); err != nil { + return nil, fmt.Errorf("cannot find repository %s/%s: %w", namespace, repositoryName, err) + } + + repo, err := p.RepoOpener.OpenRepository(ctx, &resolved) + if err != nil { + return nil, err + } + + revisions, err := repo.ListPackageRevisions(ctx, ListPackageRevisionFilter{KubeObjectName: packageRef.Name}) + if err != nil { + return nil, err + } + + var revision PackageRevision + for _, rev := range revisions { + if rev.KubeObjectName() == packageRef.Name { + revision = rev + break + } + } + if revision == nil { + return nil, fmt.Errorf("cannot find package revision %q", packageRef.Name) + } + + return revision, nil +} + +func (p *PackageFetcher) FetchResources(ctx context.Context, packageRef *api.PackageRevisionRef, namespace string) (*api.PackageRevisionResources, error) { + revision, err := p.FetchRevision(ctx, packageRef, namespace) + if err != nil { + return nil, err + } + + resources, err := revision.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot read contents of package %q: %w", packageRef.Name, err) + } + return resources, nil +} + +func BuildPackageConfig(ctx context.Context, obj *api.PackageRevision, parent PackageRevision) (*builtins.PackageConfig, error) { + config := &builtins.PackageConfig{} + + parentPath := "" + + var parentConfig *unstructured.Unstructured + if parent != nil { + parentObj, err := parent.GetPackageRevision(ctx) + if err != nil { + return nil, err + } + parentPath = parentObj.Spec.PackageName + + resources, err := parent.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("error getting resources from parent package %q: %w", parentObj.Name, err) + } + configMapObj, err := extractContextConfigMap(resources.Spec.Resources) + if err != nil { + return nil, fmt.Errorf("error getting configuration from parent package %q: %w", parentObj.Name, err) + } + parentConfig = configMapObj + + if parentConfig != nil { + // TODO: Should we support kinds other than configmaps? + var parentConfigMap corev1.ConfigMap + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(parentConfig.Object, &parentConfigMap); err != nil { + return nil, fmt.Errorf("error parsing ConfigMap from parent configuration: %w", err) + } + if s := parentConfigMap.Data[builtins.ConfigKeyPackagePath]; s != "" { + parentPath = s + "/" + parentPath + } + } + } + + if parentPath == "" { + config.PackagePath = obj.Spec.PackageName + } else { + config.PackagePath = parentPath + "/" + obj.Spec.PackageName + } + + return config, nil +} + +// ExtractContextConfigMap returns the package-context configmap, if found +func extractContextConfigMap(resources map[string]string) (*unstructured.Unstructured, error) { + unstructureds, err := objects.Parser{}.AsUnstructureds(resources) + if err != nil { + return nil, err + } + + var matches []*unstructured.Unstructured + for _, o := range unstructureds { + configMapGK := schema.GroupKind{Kind: "ConfigMap"} + if o.GroupVersionKind().GroupKind() == configMapGK { + if o.GetName() == builtins.PkgContextName { + matches = append(matches, o) + } + } + } + if len(matches) == 0 { + return nil, nil + } + + if len(matches) > 1 { + return nil, fmt.Errorf("found multiple configmaps matching name %q", builtins.PkgContextFile) + } + + return matches[0], nil +} diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 96e21c8e..91f620de 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -21,6 +21,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport" "github.com/nephio-project/porch/api/porch/v1alpha1" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/meta" "k8s.io/apimachinery/pkg/types" ) @@ -95,6 +96,9 @@ type PackageRevision interface { ResourceVersion() string ToMainPackageRevision() PackageRevision + + GetMeta() meta.PackageRevisionMeta + SetMeta(meta.PackageRevisionMeta) } // Package is an abstract package. @@ -214,6 +218,9 @@ type Repository interface { // Close cleans up any resources associated with the repository Close() error + + // Refresh the repository + Refresh(ctx context.Context) error } // The definitions below would be more appropriately located in a package usable by any Porch component. diff --git a/pkg/engine/testing.go b/pkg/repository/testing.go similarity index 86% rename from pkg/engine/testing.go rename to pkg/repository/testing.go index bcdd3078..6a37f03f 100644 --- a/pkg/engine/testing.go +++ b/pkg/repository/testing.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "fmt" @@ -20,11 +20,9 @@ import ( "os" "path/filepath" "testing" - - "github.com/nephio-project/porch/pkg/repository" ) -func readPackage(t *testing.T, packageDir string) repository.PackageResources { +func ReadPackage(t *testing.T, packageDir string) PackageResources { results := map[string]string{} if err := filepath.Walk(packageDir, func(p string, info fs.FileInfo, err error) error { @@ -49,12 +47,12 @@ func readPackage(t *testing.T, packageDir string) repository.PackageResources { }); err != nil { t.Errorf("Failed to read package from disk %q: %v", packageDir, err) } - return repository.PackageResources{ + return PackageResources{ Contents: results, } } -func writePackage(t *testing.T, packageDir string, contents repository.PackageResources) { +func WritePackage(t *testing.T, packageDir string, contents PackageResources) { for k, v := range contents.Contents { abs := filepath.Join(packageDir, k) dir := filepath.Dir(abs) diff --git a/pkg/engine/update.go b/pkg/repository/update.go similarity index 78% rename from pkg/engine/update.go rename to pkg/repository/update.go index 633f02b9..0843e2f6 100644 --- a/pkg/engine/update.go +++ b/pkg/repository/update.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" @@ -22,57 +22,56 @@ import ( "path/filepath" "github.com/nephio-project/porch/internal/kpt/util/update" - "github.com/nephio-project/porch/pkg/repository" ) // defaultPackageUpdater implements packageUpdater interface. -type defaultPackageUpdater struct{} +type DefaultPackageUpdater struct{} -func (m *defaultPackageUpdater) Update( +func (m *DefaultPackageUpdater) Update( ctx context.Context, localResources, originalResources, - upstreamResources repository.PackageResources) (updatedResources repository.PackageResources, err error) { + upstreamResources PackageResources) (updatedResources PackageResources, err error) { localDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(localDir) originalDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(originalDir) upstreamDir, err := os.MkdirTemp("", "kpt-pkg-update-*") if err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } defer os.RemoveAll(upstreamDir) if err := writeResourcesToDirectory(localDir, localResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := writeResourcesToDirectory(originalDir, originalResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := writeResourcesToDirectory(upstreamDir, upstreamResources); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } if err := m.do(ctx, localDir, originalDir, upstreamDir); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } return loadResourcesFromDirectory(localDir) } // PkgUpdate is a wrapper around `kpt pkg update`, running it against the package in packageDir -func (m *defaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDir, upstreamPkgDir string) error { +func (m *DefaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDir, upstreamPkgDir string) error { relPath := "." localPath := filepath.Join(localPkgDir, relPath) updatedPath := filepath.Join(upstreamPkgDir, relPath) @@ -94,7 +93,7 @@ func (m *defaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDi return nil } -func writeResourcesToDirectory(dir string, resources repository.PackageResources) error { +func writeResourcesToDirectory(dir string, resources PackageResources) error { for k, v := range resources.Contents { p := filepath.Join(dir, k) dir := filepath.Dir(p) @@ -108,9 +107,9 @@ func writeResourcesToDirectory(dir string, resources repository.PackageResources return nil } -func loadResourcesFromDirectory(dir string) (repository.PackageResources, error) { +func loadResourcesFromDirectory(dir string) (PackageResources, error) { // TODO: return abstraction instead of loading everything - result := repository.PackageResources{ + result := PackageResources{ Contents: map[string]string{}, } if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { @@ -132,7 +131,7 @@ func loadResourcesFromDirectory(dir string) (repository.PackageResources, error) result.Contents[rel] = string(contents) return nil }); err != nil { - return repository.PackageResources{}, err + return PackageResources{}, err } return result, nil diff --git a/pkg/engine/update_test.go b/pkg/repository/update_test.go similarity index 97% rename from pkg/engine/update_test.go rename to pkg/repository/update_test.go index 4acac1ec..e10a25d1 100644 --- a/pkg/engine/update_test.go +++ b/pkg/repository/update_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package repository import ( "context" @@ -23,7 +23,7 @@ import ( ) func TestPkgUpdate(t *testing.T) { - dfUpdater := &defaultPackageUpdater{} + dfUpdater := &DefaultPackageUpdater{} testdata, err := filepath.Abs(filepath.Join(".", "testdata", "update")) if err != nil { diff --git a/pkg/engine/builtin.go b/pkg/task/builtin.go similarity index 99% rename from pkg/engine/builtin.go rename to pkg/task/builtin.go index ff696a07..8f93daa1 100644 --- a/pkg/engine/builtin.go +++ b/pkg/task/builtin.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/builtin_test.go b/pkg/task/builtin_test.go similarity index 87% rename from pkg/engine/builtin_test.go rename to pkg/task/builtin_test.go index 10342528..29dd5181 100644 --- a/pkg/engine/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -22,6 +22,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/pkg/repository" ) const ( @@ -34,7 +35,7 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to find testdata: %v", err) } - input := readPackage(t, filepath.Join(testdata, "input")) + input := repository.ReadPackage(t, filepath.Join(testdata, "input")) packageConfig := &builtins.PackageConfig{ PackagePath: "parent1/parent1.2/parent1.2.3/me", @@ -56,10 +57,10 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to update golden files: %v", err) } - writePackage(t, expectedPackage, got) + repository.WritePackage(t, expectedPackage, got) } - want := readPackage(t, expectedPackage) + want := repository.ReadPackage(t, expectedPackage) if !cmp.Equal(want, got) { t.Errorf("Unexpected result of builtin function mutation (-want, +got): %s", cmp.Diff(want, got)) diff --git a/pkg/engine/clone.go b/pkg/task/clone.go similarity index 93% rename from pkg/engine/clone.go rename to pkg/task/clone.go index 0bc32e59..6bd35c00 100644 --- a/pkg/engine/clone.go +++ b/pkg/task/clone.go @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" "errors" "fmt" "os" - "strings" api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" @@ -41,9 +40,9 @@ type clonePackageMutation struct { name string // package target name isDeployment bool // is the package deployable instance - repoOpener RepositoryOpener + repoOpener repository.RepositoryOpener credentialResolver repository.CredentialResolver - referenceResolver ReferenceResolver + referenceResolver repository.ReferenceResolver // packageConfig contains the package configuration. packageConfig *builtins.PackageConfig @@ -107,9 +106,9 @@ func (m *clonePackageMutation) cloneFromRegisteredRepository(ctx context.Context return repository.PackageResources{}, fmt.Errorf("upstreamRef.name is required") } - upstreamRevision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, }).FetchRevision(ctx, ref, m.namespace) if err != nil { return repository.PackageResources{}, fmt.Errorf("failed to fetch package revision %q: %w", ref.Name, err) @@ -196,11 +195,3 @@ func (m *clonePackageMutation) cloneFromGit(ctx context.Context, gitPackage *api func (m *clonePackageMutation) cloneFromOci(_ context.Context, _ *api.OciPackage) (repository.PackageResources, error) { return repository.PackageResources{}, errors.New("clone from OCI is not implemented") } - -func parseUpstreamRepository(name string) (string, error) { - lastDash := strings.LastIndex(name, "-") - if lastDash < 0 { - return "", fmt.Errorf("malformed package revision name; expected at least one hyphen: %q", name) - } - return name[:lastDash], nil -} diff --git a/pkg/engine/clone_test.go b/pkg/task/clone_test.go similarity index 99% rename from pkg/engine/clone_test.go rename to pkg/task/clone_test.go index e9930ce5..4e773353 100644 --- a/pkg/engine/clone_test.go +++ b/pkg/task/clone_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/edit.go b/pkg/task/edit.go similarity index 90% rename from pkg/engine/edit.go rename to pkg/task/edit.go index 0578efcb..f6cd645d 100644 --- a/pkg/engine/edit.go +++ b/pkg/task/edit.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -28,8 +28,8 @@ type editPackageMutation struct { namespace string repositoryName string packageName string - repoOpener RepositoryOpener - referenceResolver ReferenceResolver + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver } var _ mutation = &editPackageMutation{} @@ -40,9 +40,9 @@ func (m *editPackageMutation) Apply(ctx context.Context, resources repository.Pa sourceRef := m.task.Edit.Source - revision, err := (&PackageFetcher{ - repoOpener: m.repoOpener, - referenceResolver: m.referenceResolver, + revision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, }).FetchRevision(ctx, sourceRef, m.namespace) if err != nil { return repository.PackageResources{}, nil, fmt.Errorf("failed to fetch package %q: %w", sourceRef.Name, err) diff --git a/pkg/engine/edit_test.go b/pkg/task/edit_test.go similarity index 94% rename from pkg/engine/edit_test.go rename to pkg/task/edit_test.go index 0e46163f..1c48d15d 100644 --- a/pkg/engine/edit_test.go +++ b/pkg/task/edit_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -22,9 +22,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/engine/fake" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" ) func TestEdit(t *testing.T) { @@ -32,7 +32,7 @@ func TestEdit(t *testing.T) { packageName := "repo-1234567890" repositoryName := "repo" revision := "v1" - packageRevision := &fake.PackageRevision{ + packageRevision := &fake.FakePackageRevision{ Name: packageName, PackageRevisionKey: repository.PackageRevisionKey{ Package: pkg, @@ -110,7 +110,7 @@ info: // Implementation of the ReferenceResolver interface for testing. type fakeReferenceResolver struct{} -func (f *fakeReferenceResolver) ResolveReference(ctx context.Context, namespace, name string, result Object) error { +func (f *fakeReferenceResolver) ResolveReference(ctx context.Context, namespace, name string, result repository.Object) error { return nil } diff --git a/pkg/engine/eval.go b/pkg/task/eval.go similarity index 99% rename from pkg/engine/eval.go rename to pkg/task/eval.go index db2b4e1f..3bbf68b3 100644 --- a/pkg/engine/eval.go +++ b/pkg/task/eval.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/init.go b/pkg/task/init.go similarity index 99% rename from pkg/engine/init.go rename to pkg/task/init.go index 8b88a77e..a279f732 100644 --- a/pkg/engine/init.go +++ b/pkg/task/init.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/init_test.go b/pkg/task/init_test.go similarity index 99% rename from pkg/engine/init_test.go rename to pkg/task/init_test.go index c5850858..0593f1aa 100644 --- a/pkg/engine/init_test.go +++ b/pkg/task/init_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/kio.go b/pkg/task/kio.go similarity index 99% rename from pkg/engine/kio.go rename to pkg/task/kio.go index 8b1740f2..6b253a72 100644 --- a/pkg/engine/kio.go +++ b/pkg/task/kio.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/mergekey.go b/pkg/task/mergekey.go similarity index 99% rename from pkg/engine/mergekey.go rename to pkg/task/mergekey.go index c3327c3d..93d97579 100644 --- a/pkg/engine/mergekey.go +++ b/pkg/task/mergekey.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/engine_test.go b/pkg/task/patch_test.go similarity index 96% rename from pkg/engine/engine_test.go rename to pkg/task/patch_test.go index 0248d6bf..eb116388 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/task/patch_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" @@ -21,9 +21,9 @@ import ( "github.com/google/go-cmp/cmp" api "github.com/nephio-project/porch/api/porch/v1alpha1" - "github.com/nephio-project/porch/pkg/engine/fake" kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/repository" + "github.com/nephio-project/porch/pkg/repository/fake" ) func TestSomething(t *testing.T) { @@ -34,7 +34,7 @@ func TestSomething(t *testing.T) { patch api.PatchSpec }{ "no gates or conditions": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{}, }, newApiPkgRev: &api.PackageRevision{ @@ -43,7 +43,7 @@ func TestSomething(t *testing.T) { hasPatch: false, }, "first gate and condition added": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{}, }, newApiPkgRev: &api.PackageRevision{ @@ -83,7 +83,7 @@ func TestSomething(t *testing.T) { }, }, "additional readinessGates and conditions added": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ @@ -156,7 +156,7 @@ func TestSomething(t *testing.T) { }, }, "no changes": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ @@ -217,7 +217,7 @@ func TestSomething(t *testing.T) { hasPatch: false, }, "readinessGates and conditions removed": { - repoPkgRev: &fake.PackageRevision{ + repoPkgRev: &fake.FakePackageRevision{ Kptfile: kptfile.KptFile{ Info: &kptfile.PackageInfo{ ReadinessGates: []kptfile.ReadinessGate{ diff --git a/pkg/engine/patchgen.go b/pkg/task/patchgen.go similarity index 99% rename from pkg/engine/patchgen.go rename to pkg/task/patchgen.go index b09fb01f..e27b14ed 100644 --- a/pkg/engine/patchgen.go +++ b/pkg/task/patchgen.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/patchgen_test.go b/pkg/task/patchgen_test.go similarity index 99% rename from pkg/engine/patchgen_test.go rename to pkg/task/patchgen_test.go index 9432d9a4..57aaeacc 100644 --- a/pkg/engine/patchgen_test.go +++ b/pkg/task/patchgen_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" diff --git a/pkg/engine/render.go b/pkg/task/render.go similarity index 99% rename from pkg/engine/render.go rename to pkg/task/render.go index 6219b054..31d5744b 100644 --- a/pkg/engine/render.go +++ b/pkg/task/render.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/render_test.go b/pkg/task/render_test.go similarity index 99% rename from pkg/engine/render_test.go rename to pkg/task/render_test.go index 745491ce..bf24b6b1 100644 --- a/pkg/engine/render_test.go +++ b/pkg/task/render_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "context" diff --git a/pkg/engine/replace_test.go b/pkg/task/replace_test.go similarity index 96% rename from pkg/engine/replace_test.go rename to pkg/task/replace_test.go index a3c9fe50..fc29a4cc 100644 --- a/pkg/engine/replace_test.go +++ b/pkg/task/replace_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package task import ( "bytes" @@ -30,7 +30,7 @@ import ( func TestReplaceResources(t *testing.T) { ctx := context.Background() - input := readPackage(t, filepath.Join("testdata", "replace")) + input := repository.ReadPackage(t, filepath.Join("testdata", "replace")) nocomment := removeComments(t, input) replace := &mutationReplaceResources{ diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go new file mode 100644 index 00000000..5ea48726 --- /dev/null +++ b/pkg/task/taskhandler.go @@ -0,0 +1,675 @@ +// Copyright 2022,2024 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package task + +import ( + "bytes" + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/internal/kpt/fnruntime" + "github.com/nephio-project/porch/pkg/kpt" + kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/kpt/fn" + "github.com/nephio-project/porch/pkg/repository" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/kustomize/kyaml/comments" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var tracer = otel.Tracer("task") + +type TaskHandler struct { + RunnerOptionsResolver func(namespace string) fnruntime.RunnerOptions + Runtime fn.FunctionRuntime + RepoOpener repository.RepositoryOpener + CredentialResolver repository.CredentialResolver + ReferenceResolver repository.ReferenceResolver +} + +type mutation interface { + Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) +} + +func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { + for _, tt := range oneOf { + if taskType == tt { + return true + } + } + return false +} + +func (th *TaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { + var mutations []mutation + + // Unless first task is Init or Clone, insert Init to create an empty package. + tasks := obj.Spec.Tasks + if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { + mutations = append(mutations, &initPackageMutation{ + name: obj.Spec.PackageName, + task: &api.Task{ + Init: &api.PackageInitTaskSpec{ + Subpackage: "", + Description: fmt.Sprintf("%s description", obj.Spec.PackageName), + }, + }, + }) + } + + for i := range tasks { + task := &tasks[i] + mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) + if err != nil { + return err + } + mutations = append(mutations, mutation) + } + + // Render package after creation. + mutations = th.conditionalAddRender(obj, mutations) + + baseResources := repository.PackageResources{} + if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { + return err + } + + return nil +} + +func (th *TaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { + switch task.Type { + case api.TaskTypeInit: + if task.Init == nil { + return nil, fmt.Errorf("init not set for task of type %q", task.Type) + } + return &initPackageMutation{ + name: obj.Spec.PackageName, + task: task, + }, nil + case api.TaskTypeClone: + if task.Clone == nil { + return nil, fmt.Errorf("clone not set for task of type %q", task.Type) + } + return &clonePackageMutation{ + task: task, + namespace: obj.Namespace, + name: obj.Spec.PackageName, + isDeployment: isDeployment, + repoOpener: th.RepoOpener, + credentialResolver: th.CredentialResolver, + referenceResolver: th.ReferenceResolver, + packageConfig: packageConfig, + }, nil + + case api.TaskTypeUpdate: + if task.Update == nil { + return nil, fmt.Errorf("update not set for task of type %q", task.Type) + } + cloneTask := findCloneTask(obj) + if cloneTask == nil { + return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) + } + return &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: task, + namespace: obj.Namespace, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + pkgName: obj.Spec.PackageName, + }, nil + + case api.TaskTypePatch: + return buildPatchMutation(ctx, task) + + case api.TaskTypeEdit: + if task.Edit == nil { + return nil, fmt.Errorf("edit not set for task of type %q", task.Type) + } + return &editPackageMutation{ + task: task, + namespace: obj.Namespace, + packageName: obj.Spec.PackageName, + repositoryName: obj.Spec.RepositoryName, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + }, nil + + case api.TaskTypeEval: + if task.Eval == nil { + return nil, fmt.Errorf("eval not set for task of type %q", task.Type) + } + // TODO: We should find a different way to do this. Probably a separate + // task for render. + if task.Eval.Image == "render" { + runnerOptions := th.RunnerOptionsResolver(obj.Namespace) + return &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }, nil + } else { + runnerOptions := th.RunnerOptionsResolver(obj.Namespace) + return &evalFunctionMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + task: task, + }, nil + } + + default: + return nil, fmt.Errorf("task of type %q not supported", task.Type) + } +} + +func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { + kf, err := oldPackage.GetKptfile(ctx) + if err != nil { + return nil, false, err + } + + var orgKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + orgKfString = buf.String() + } + + var readinessGates []kptfile.ReadinessGate + for _, rg := range newObj.Spec.ReadinessGates { + readinessGates = append(readinessGates, kptfile.ReadinessGate{ + ConditionType: rg.ConditionType, + }) + } + + var conditions []kptfile.Condition + for _, c := range newObj.Status.Conditions { + conditions = append(conditions, kptfile.Condition{ + Type: c.Type, + Status: convertStatusToKptfile(c.Status), + Reason: c.Reason, + Message: c.Message, + }) + } + + if kf.Info == nil && len(readinessGates) > 0 { + kf.Info = &kptfile.PackageInfo{} + } + if len(readinessGates) > 0 { + kf.Info.ReadinessGates = readinessGates + } + + if kf.Status == nil && len(conditions) > 0 { + kf.Status = &kptfile.Status{} + } + if len(conditions) > 0 { + kf.Status.Conditions = conditions + } + + var newKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + newKfString = buf.String() + } + patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) + if err != nil { + return nil, false, err + } + // If patch is empty, don't create a Task. + if patchSpec.Contents == "" { + return nil, false, nil + } + + return &api.Task{ + Type: api.TaskTypePatch, + Patch: &api.PackagePatchTaskSpec{ + Patches: []api.PatchSpec{ + patchSpec, + }, + }, + }, true, nil +} + +func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { + switch s { + case api.ConditionTrue: + return kptfile.ConditionTrue + case api.ConditionFalse: + return kptfile.ConditionFalse + case api.ConditionUnknown: + return kptfile.ConditionUnknown + default: + panic(fmt.Errorf("unknown condition status: %v", s)) + } +} + +// conditionalAddRender adds a render mutation to the end of the mutations slice if the last +// entry is not already a render mutation. +func (th *TaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { + if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { + return mutations + } + + runnerOptions := th.RunnerOptionsResolver(subject.GetNamespace()) + + return append(mutations, &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }) +} + +func isRenderMutation(m mutation) bool { + _, isRender := m.(*renderPackageMutation) + return isRender +} + +// applyResourceMutations mutates the resources and returns the most recent renderResult. +func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { + var lastApplied mutation + for _, m := range mutations { + updatedResources, taskResult, err := m.Apply(ctx, baseResources) + if taskResult == nil && err == nil { + // a nil taskResult means nothing changed + continue + } + + var task *api.Task + if taskResult != nil { + task = taskResult.Task + } + if taskResult != nil && task.Type == api.TaskTypeEval { + renderStatus = taskResult.RenderStatus + } + if err != nil { + return updatedResources, renderStatus, err + } + + // if the last applied mutation was a render mutation, and so is this one, skip it + if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { + continue + } + lastApplied = m + + if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ + Spec: api.PackageRevisionResourcesSpec{ + Resources: updatedResources.Contents, + }, + }, task); err != nil { + return updatedResources, renderStatus, err + } + baseResources = updatedResources + applied = updatedResources + } + + return applied, renderStatus, nil +} + +type updatePackageMutation struct { + cloneTask *api.Task + updateTask *api.Task + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver + namespace string + pkgName string +} + +func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) + defer span.End() + + currUpstreamPkgRef, err := m.currUpstream() + if err != nil { + return repository.PackageResources{}, nil, err + } + + targetUpstream := m.updateTask.Update.Upstream + if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { + return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") + } + + originalResources, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", + m.pkgName, *currUpstreamPkgRef) + } + + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) + } + upstreamResources, err := upstreamRevision.GetResources(ctx) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) + } + + klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", + m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) + + // May be have packageUpdater part of the Porch core to make it easy for testing ? + updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, + resources, + repository.PackageResources{ + Contents: originalResources.Spec.Resources, + }, + repository.PackageResources{ + Contents: upstreamResources.Spec.Resources, + }) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) + } + + newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) + } + if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) + } + + // ensure merge-key comment is added to newly added resources. + result, err := ensureMergeKey(ctx, updatedResources) + if err != nil { + klog.Infof("failed to add merge key comments: %v", err) + } + return result, &api.TaskResult{Task: m.updateTask}, nil +} + +// Currently assumption is that downstream packages will be forked from a porch package. +// As per current implementation, upstream package ref is stored in a new update task but this may +// change so the logic of figuring out current upstream will live in this function. +func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { + if m.cloneTask == nil || m.cloneTask.Clone == nil { + return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) + } + upstream := m.cloneTask.Clone.Upstream + if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { + return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) + } + return upstream.UpstreamRef, nil +} + +func findCloneTask(pr *api.PackageRevision) *api.Task { + if len(pr.Spec.Tasks) == 0 { + return nil + } + firstTask := pr.Spec.Tasks[0] + if firstTask.Type == api.TaskTypeClone { + return &firstTask + } + return nil +} + +type mutationReplaceResources struct { + newResources *api.PackageRevisionResources + oldResources *api.PackageRevisionResources +} + +func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) + defer span.End() + + patch := &api.PackagePatchTaskSpec{} + + old := resources.Contents + new, err := healConfig(old, m.newResources.Spec.Resources) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) + } + + for k, newV := range new { + oldV, ok := old[k] + // New config or changed config + if !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeCreateFile, + Contents: newV, + } + patch.Patches = append(patch.Patches, patchSpec) + } else if newV != oldV { + patchSpec, err := GeneratePatch(k, oldV, newV) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) + } + if patchSpec.Contents == "" { + continue + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + for k := range old { + // Deleted config + if _, ok := new[k]; !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeDeleteFile, + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + // If patch is empty, don't create a Task. + var taskResult *api.TaskResult + if len(patch.Patches) > 0 { + taskResult = &api.TaskResult{ + Task: &api.Task{ + Type: api.TaskTypePatch, + Patch: patch, + }, + } + } + return repository.PackageResources{Contents: new}, taskResult, nil +} + +func healConfig(old, new map[string]string) (map[string]string, error) { + // Copy comments from old config to new + oldResources, err := (&packageReader{ + input: repository.PackageResources{Contents: old}, + extra: map[string]string{}, + }).Read() + if err != nil { + return nil, fmt.Errorf("failed to read old packge resources: %w", err) + } + + var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { + for _, n := range r { + for _, original := range oldResources { + if n.GetNamespace() == original.GetNamespace() && + n.GetName() == original.GetName() && + n.GetApiVersion() == original.GetApiVersion() && + n.GetKind() == original.GetKind() { + comments.CopyComments(original, n) + } + } + } + return r, nil + } + + out := &packageWriter{ + output: repository.PackageResources{ + Contents: map[string]string{}, + }, + } + + extra := map[string]string{} + + if err := (kio.Pipeline{ + Inputs: []kio.Reader{&packageReader{ + input: repository.PackageResources{Contents: new}, + extra: extra, + }}, + Filters: []kio.Filter{filter}, + Outputs: []kio.Writer{out}, + ContinueOnEmptyResult: true, + }).Execute(); err != nil { + return nil, err + } + + healed := out.output.Contents + + for k, v := range extra { + healed[k] = v + } + + return healed, nil +} + +func (th *TaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { + ctx, span := tracer.Start(ctx, "TaskHandler::DoPRMutations", trace.WithAttributes()) + defer span.End() + + var mutations []mutation + if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { + return fmt.Errorf("removing tasks is not yet supported") + } + for i := range oldObj.Spec.Tasks { + oldTask := &oldObj.Spec.Tasks[i] + newTask := &newObj.Spec.Tasks[i] + if oldTask.Type != newTask.Type { + return fmt.Errorf("changing task types is not yet supported") + } + } + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { + return fmt.Errorf("can only append one task at a time") + } + + newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] + if newTask.Type != api.TaskTypeUpdate { + return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) + } + if newTask.Update == nil { + return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) + } + + cloneTask := findCloneTask(oldObj) + if cloneTask == nil { + return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) + } + + mutation := &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: &newTask, + repoOpener: th.RepoOpener, + referenceResolver: th.ReferenceResolver, + namespace: namespace, + pkgName: oldObj.GetName(), + } + mutations = append(mutations, mutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // If any of the fields in the API that are projections from the Kptfile + // must be updated in the Kptfile as well. + kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) + if err != nil { + return err + } + if created { + kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) + if err != nil { + return err + } + mutations = append(mutations, kfPatchMutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // TODO: Handle the case if alongside lifecycle change, tasks are changed too. + // Update package contents only if the package is in draft state + if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { + apiResources, err := repoPR.GetResources(ctx) + if err != nil { + return fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: apiResources.Spec.Resources, + } + + if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { + return err + } + } + + return nil +} + +func (th *TaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { + ctx, span := tracer.Start(ctx, "TaskHandler::DoPRResourceMutations", trace.WithAttributes()) + defer span.End() + + runnerOptions := th.RunnerOptionsResolver(oldRes.GetNamespace()) + + mutations := []mutation{ + &mutationReplaceResources{ + newResources: newRes, + oldResources: oldRes, + }, + } + prevResources, err := pr2Update.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: prevResources.Spec.Resources, + } + + appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) + if err != nil { + return nil, err + } + + var renderStatus *api.RenderStatus + if len(appliedResources.Contents) > 0 { + // render the package + // Render failure will not fail the overall API operation. + // The render error and result is captured as part of renderStatus above + // and is returned in packageresourceresources API's status field. We continue with + // saving the non-rendered resources to avoid losing user's changes. + // and supress this err. + _, renderStatus, _ = applyResourceMutations(ctx, + draft, + appliedResources, + []mutation{&renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.Runtime, + }}) + } else { + renderStatus = nil + } + + return renderStatus, nil +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 5dab0f90..c323add7 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "fmt" "os" + "strings" porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -100,3 +101,11 @@ func SchemaToMetaGVR(gvr schema.GroupVersionResource) metav1.GroupVersionResourc Resource: gvr.Resource, } } + +func ParseRepositoryName(name string) (string, error) { + lastDash := strings.LastIndex(name, "-") + if lastDash < 0 { + return "", fmt.Errorf("malformed package revision name; expected at least one hyphen: %q", name) + } + return name[:lastDash], nil +} From ea594458da996f20df0e31322460835b7ba1438a Mon Sep 17 00:00:00 2001 From: liamfallon Date: Wed, 13 Nov 2024 18:43:40 +0000 Subject: [PATCH 09/13] Fix the metadata returned to the API on packagerevisions --- pkg/cache/memory/util.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index cdb3ca1c..5f998a72 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -62,6 +62,8 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac for _, v := range latest { v.isLatestRevision = true } + + // If the } func toPackageRevisionSlice(cached map[repository.PackageRevisionKey]*cachedPackageRevision, filter repository.ListPackageRevisionFilter) []repository.PackageRevision { From 0f215846349b776a537dc66da319ffb99fadf74e Mon Sep 17 00:00:00 2001 From: liamfallon Date: Thu, 14 Nov 2024 15:18:52 +0000 Subject: [PATCH 10/13] Rebased against main --- pkg/registry/porch/watch.go | 3 -- pkg/repository/testdata/update/local/Kptfile | 27 +++++++++++++++++ .../testdata/update/local/README.md | 21 ++++++++++++++ .../testdata/update/local/namespace.yaml | 5 ++++ .../update/local/package-context.yaml | 8 +++++ .../testdata/update/local/resourcequota.yaml | 9 ++++++ .../testdata/update/local/rolebinding.yaml | 13 +++++++++ .../update/local/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/original/Kptfile | 14 +++++++++ .../testdata/update/original/README.md | 21 ++++++++++++++ .../testdata/update/original/namespace.yaml | 5 ++++ .../update/original/package-context.yaml | 8 +++++ .../update/original/resourcequota.yaml | 9 ++++++ .../testdata/update/original/rolebinding.yaml | 13 +++++++++ .../update/original/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/updated/Kptfile | 27 +++++++++++++++++ .../testdata/update/updated/README.md | 21 ++++++++++++++ .../testdata/update/updated/namespace.yaml | 5 ++++ .../update/updated/package-context.yaml | 8 +++++ .../update/updated/resourcequota.yaml | 9 ++++++ .../testdata/update/updated/rolebinding.yaml | 13 +++++++++ .../update/updated/update-rolebinding.yaml | 20 +++++++++++++ .../testdata/update/upstream/Kptfile | 14 +++++++++ .../testdata/update/upstream/README.md | 21 ++++++++++++++ .../testdata/update/upstream/namespace.yaml | 5 ++++ .../update/upstream/package-context.yaml | 8 +++++ .../update/upstream/resourcequota.yaml | 9 ++++++ .../testdata/update/upstream/rolebinding.yaml | 13 +++++++++ .../update/upstream/update-rolebinding.yaml | 20 +++++++++++++ pkg/task/testdata/clone/bucket/Kptfile | 20 +++++++++++++ pkg/task/testdata/clone/bucket/bucket.yaml | 26 +++++++++++++++++ pkg/task/testdata/clone/configmap/Kptfile | 20 +++++++++++++ .../testdata/clone/configmap/configmap.yaml | 21 ++++++++++++++ pkg/task/testdata/context/expected/Kptfile | 6 ++++ .../testdata/context/expected/bucket.yaml | 12 ++++++++ .../context/expected/package-context.yaml | 9 ++++++ pkg/task/testdata/context/input/Kptfile | 6 ++++ pkg/task/testdata/context/input/bucket.yaml | 12 ++++++++ pkg/task/testdata/init/testpkg/Kptfile | 13 +++++++++ pkg/task/testdata/init/testpkg/README.md | 21 ++++++++++++++ .../init/testpkg/package-context.yaml | 8 +++++ pkg/task/testdata/replace/Kptfile | 23 +++++++++++++++ pkg/task/testdata/replace/README.md | 1 + pkg/task/testdata/replace/bucket.yaml | 18 ++++++++++++ pkg/task/testdata/simple-render/expected.txt | 27 +++++++++++++++++ .../simple-render/simple-bucket/Kptfile | 29 +++++++++++++++++++ .../simple-render/simple-bucket/bucket.yaml | 26 +++++++++++++++++ 47 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 pkg/repository/testdata/update/local/Kptfile create mode 100644 pkg/repository/testdata/update/local/README.md create mode 100644 pkg/repository/testdata/update/local/namespace.yaml create mode 100644 pkg/repository/testdata/update/local/package-context.yaml create mode 100644 pkg/repository/testdata/update/local/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/local/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/local/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/original/Kptfile create mode 100644 pkg/repository/testdata/update/original/README.md create mode 100644 pkg/repository/testdata/update/original/namespace.yaml create mode 100644 pkg/repository/testdata/update/original/package-context.yaml create mode 100644 pkg/repository/testdata/update/original/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/original/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/original/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/updated/Kptfile create mode 100644 pkg/repository/testdata/update/updated/README.md create mode 100644 pkg/repository/testdata/update/updated/namespace.yaml create mode 100644 pkg/repository/testdata/update/updated/package-context.yaml create mode 100644 pkg/repository/testdata/update/updated/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/updated/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/updated/update-rolebinding.yaml create mode 100644 pkg/repository/testdata/update/upstream/Kptfile create mode 100644 pkg/repository/testdata/update/upstream/README.md create mode 100644 pkg/repository/testdata/update/upstream/namespace.yaml create mode 100644 pkg/repository/testdata/update/upstream/package-context.yaml create mode 100644 pkg/repository/testdata/update/upstream/resourcequota.yaml create mode 100644 pkg/repository/testdata/update/upstream/rolebinding.yaml create mode 100644 pkg/repository/testdata/update/upstream/update-rolebinding.yaml create mode 100644 pkg/task/testdata/clone/bucket/Kptfile create mode 100644 pkg/task/testdata/clone/bucket/bucket.yaml create mode 100644 pkg/task/testdata/clone/configmap/Kptfile create mode 100644 pkg/task/testdata/clone/configmap/configmap.yaml create mode 100644 pkg/task/testdata/context/expected/Kptfile create mode 100644 pkg/task/testdata/context/expected/bucket.yaml create mode 100644 pkg/task/testdata/context/expected/package-context.yaml create mode 100644 pkg/task/testdata/context/input/Kptfile create mode 100644 pkg/task/testdata/context/input/bucket.yaml create mode 100644 pkg/task/testdata/init/testpkg/Kptfile create mode 100644 pkg/task/testdata/init/testpkg/README.md create mode 100644 pkg/task/testdata/init/testpkg/package-context.yaml create mode 100644 pkg/task/testdata/replace/Kptfile create mode 100644 pkg/task/testdata/replace/README.md create mode 100644 pkg/task/testdata/replace/bucket.yaml create mode 100644 pkg/task/testdata/simple-render/expected.txt create mode 100644 pkg/task/testdata/simple-render/simple-bucket/Kptfile create mode 100644 pkg/task/testdata/simple-render/simple-bucket/bucket.yaml diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index 3b721046..4f5de58f 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -238,9 +238,6 @@ func (w *watcher) sendWatchEvent(ev watch.Event) { // TODO: Handle the case that the watch channel is full? w.resultChan <- ev w.totalSent += 1 - if (w.totalSent % 100) == 0 { - klog.Infof("watch %p: total sent: %d", w, w.totalSent) - } } // OnPackageRevisionChange is the callback called when a PackageRevision changes. diff --git a/pkg/repository/testdata/update/local/Kptfile b/pkg/repository/testdata/update/local/Kptfile new file mode 100644 index 00000000..56adbced --- /dev/null +++ b/pkg/repository/testdata/update/local/Kptfile @@ -0,0 +1,27 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: backend + annotations: + config.kubernetes.io/local-config: "true" +upstream: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 +upstreamLock: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 + commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/local/README.md b/pkg/repository/testdata/update/local/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/local/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/local/namespace.yaml b/pkg/repository/testdata/update/local/namespace.yaml new file mode 100644 index 00000000..5a044679 --- /dev/null +++ b/pkg/repository/testdata/update/local/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: # kpt-merge: example + name: backend +spec: {} diff --git a/pkg/repository/testdata/update/local/package-context.yaml b/pkg/repository/testdata/update/local/package-context.yaml new file mode 100644 index 00000000..9b77f45e --- /dev/null +++ b/pkg/repository/testdata/update/local/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: backend diff --git a/pkg/repository/testdata/update/local/resourcequota.yaml b/pkg/repository/testdata/update/local/resourcequota.yaml new file mode 100644 index 00000000..02e22b08 --- /dev/null +++ b/pkg/repository/testdata/update/local/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: # kpt-merge: example/default + name: default + namespace: backend +spec: + hard: + cpu: "40" + memory: 40G diff --git a/pkg/repository/testdata/update/local/rolebinding.yaml b/pkg/repository/testdata/update/local/rolebinding.yaml new file mode 100644 index 00000000..f57f1f2e --- /dev/null +++ b/pkg/repository/testdata/update/local/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: # kpt-merge: example/app-admin + name: app-admin + namespace: backend +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: backend.admin@bigco.com diff --git a/pkg/repository/testdata/update/local/update-rolebinding.yaml b/pkg/repository/testdata/update/local/update-rolebinding.yaml new file mode 100644 index 00000000..6fdff582 --- /dev/null +++ b/pkg/repository/testdata/update/local/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: # kpt-merge: example/update-rolebinding + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/original/Kptfile b/pkg/repository/testdata/update/original/Kptfile new file mode 100644 index 00000000..db627135 --- /dev/null +++ b/pkg/repository/testdata/update/original/Kptfile @@ -0,0 +1,14 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: basens + annotations: + config.kubernetes.io/local-config: "true" +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/original/README.md b/pkg/repository/testdata/update/original/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/original/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/original/namespace.yaml b/pkg/repository/testdata/update/original/namespace.yaml new file mode 100644 index 00000000..9219ae32 --- /dev/null +++ b/pkg/repository/testdata/update/original/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: example +spec: {} diff --git a/pkg/repository/testdata/update/original/package-context.yaml b/pkg/repository/testdata/update/original/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/repository/testdata/update/original/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/repository/testdata/update/original/resourcequota.yaml b/pkg/repository/testdata/update/original/resourcequota.yaml new file mode 100644 index 00000000..a5c7f9dc --- /dev/null +++ b/pkg/repository/testdata/update/original/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: default + namespace: example +spec: + hard: + cpu: "40" + memory: 40G diff --git a/pkg/repository/testdata/update/original/rolebinding.yaml b/pkg/repository/testdata/update/original/rolebinding.yaml new file mode 100644 index 00000000..12ce8875 --- /dev/null +++ b/pkg/repository/testdata/update/original/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: app-admin + namespace: example +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: example.admin@bigco.com diff --git a/pkg/repository/testdata/update/original/update-rolebinding.yaml b/pkg/repository/testdata/update/original/update-rolebinding.yaml new file mode 100644 index 00000000..a20edf50 --- /dev/null +++ b/pkg/repository/testdata/update/original/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/updated/Kptfile b/pkg/repository/testdata/update/updated/Kptfile new file mode 100644 index 00000000..56adbced --- /dev/null +++ b/pkg/repository/testdata/update/updated/Kptfile @@ -0,0 +1,27 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: backend + annotations: + config.kubernetes.io/local-config: "true" +upstream: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 +upstreamLock: + type: git + git: + repo: https://github.com/droot/pkg-catalog.git + directory: basens + ref: basens/v0 + commit: b3e1d439516a5e8d49adc0c82d3e95578570dbfa +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/updated/README.md b/pkg/repository/testdata/update/updated/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/updated/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/updated/namespace.yaml b/pkg/repository/testdata/update/updated/namespace.yaml new file mode 100644 index 00000000..5a044679 --- /dev/null +++ b/pkg/repository/testdata/update/updated/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: # kpt-merge: example + name: backend +spec: {} diff --git a/pkg/repository/testdata/update/updated/package-context.yaml b/pkg/repository/testdata/update/updated/package-context.yaml new file mode 100644 index 00000000..9b77f45e --- /dev/null +++ b/pkg/repository/testdata/update/updated/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: backend diff --git a/pkg/repository/testdata/update/updated/resourcequota.yaml b/pkg/repository/testdata/update/updated/resourcequota.yaml new file mode 100644 index 00000000..e7bc953e --- /dev/null +++ b/pkg/repository/testdata/update/updated/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: # kpt-merge: example/default + name: default + namespace: backend +spec: + hard: + cpu: "40" + memory: 60G diff --git a/pkg/repository/testdata/update/updated/rolebinding.yaml b/pkg/repository/testdata/update/updated/rolebinding.yaml new file mode 100644 index 00000000..f57f1f2e --- /dev/null +++ b/pkg/repository/testdata/update/updated/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: # kpt-merge: example/app-admin + name: app-admin + namespace: backend +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: backend.admin@bigco.com diff --git a/pkg/repository/testdata/update/updated/update-rolebinding.yaml b/pkg/repository/testdata/update/updated/update-rolebinding.yaml new file mode 100644 index 00000000..6fdff582 --- /dev/null +++ b/pkg/repository/testdata/update/updated/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: # kpt-merge: example/update-rolebinding + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/repository/testdata/update/upstream/Kptfile b/pkg/repository/testdata/update/upstream/Kptfile new file mode 100644 index 00000000..65fe6a65 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/Kptfile @@ -0,0 +1,14 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: basens + annotations: + config.kubernetes.io/local-config: "true" +info: + description: kpt package for provisioning namespace +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.3.4 + configPath: package-context.yaml + - image: gcr.io/kpt-fn/apply-replacements:v0.1.0 + configPath: update-rolebinding.yaml diff --git a/pkg/repository/testdata/update/upstream/README.md b/pkg/repository/testdata/update/upstream/README.md new file mode 100644 index 00000000..2b9db73e --- /dev/null +++ b/pkg/repository/testdata/update/upstream/README.md @@ -0,0 +1,21 @@ +# basens + +## Description +kpt package for provisioning namespace + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] basens` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree basens` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init basens +kpt live apply basens --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/repository/testdata/update/upstream/namespace.yaml b/pkg/repository/testdata/update/upstream/namespace.yaml new file mode 100644 index 00000000..9219ae32 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: example +spec: {} diff --git a/pkg/repository/testdata/update/upstream/package-context.yaml b/pkg/repository/testdata/update/upstream/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/repository/testdata/update/upstream/resourcequota.yaml b/pkg/repository/testdata/update/upstream/resourcequota.yaml new file mode 100644 index 00000000..5a56078d --- /dev/null +++ b/pkg/repository/testdata/update/upstream/resourcequota.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ResourceQuota +metadata: + name: default + namespace: example +spec: + hard: + cpu: "40" + memory: 60G diff --git a/pkg/repository/testdata/update/upstream/rolebinding.yaml b/pkg/repository/testdata/update/upstream/rolebinding.yaml new file mode 100644 index 00000000..12ce8875 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: app-admin + namespace: example +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: app-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: example.admin@bigco.com diff --git a/pkg/repository/testdata/update/upstream/update-rolebinding.yaml b/pkg/repository/testdata/update/upstream/update-rolebinding.yaml new file mode 100644 index 00000000..a20edf50 --- /dev/null +++ b/pkg/repository/testdata/update/upstream/update-rolebinding.yaml @@ -0,0 +1,20 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: ApplyReplacements +metadata: + name: update-rolebinding + annotations: + config.kubernetes.io/local-config: "true" +replacements: +- source: + kind: ConfigMap + name: kptfile.kpt.dev + fieldPath: data.name + targets: + - select: + name: app-admin + kind: RoleBinding + fieldPaths: + - subjects.[kind=Group].name + options: + delimiter: '.' + index: 0 diff --git a/pkg/task/testdata/clone/bucket/Kptfile b/pkg/task/testdata/clone/bucket/Kptfile new file mode 100644 index 00000000..e2b651e4 --- /dev/null +++ b/pkg/task/testdata/clone/bucket/Kptfile @@ -0,0 +1,20 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: bucket +info: + description: Bucket test package diff --git a/pkg/task/testdata/clone/bucket/bucket.yaml b/pkg/task/testdata/clone/bucket/bucket.yaml new file mode 100644 index 00000000..9b75ffcb --- /dev/null +++ b/pkg/task/testdata/clone/bucket/bucket.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/clone/configmap/Kptfile b/pkg/task/testdata/clone/configmap/Kptfile new file mode 100644 index 00000000..54fdea56 --- /dev/null +++ b/pkg/task/testdata/clone/configmap/Kptfile @@ -0,0 +1,20 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: configmap +info: + description: ConfigMap test package diff --git a/pkg/task/testdata/clone/configmap/configmap.yaml b/pkg/task/testdata/clone/configmap/configmap.yaml new file mode 100644 index 00000000..8b4c9896 --- /dev/null +++ b/pkg/task/testdata/clone/configmap/configmap.yaml @@ -0,0 +1,21 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-name + namespace: configmap-namespace +data: + key: value diff --git a/pkg/task/testdata/context/expected/Kptfile b/pkg/task/testdata/context/expected/Kptfile new file mode 100644 index 00000000..1e92a6cf --- /dev/null +++ b/pkg/task/testdata/context/expected/Kptfile @@ -0,0 +1,6 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: input +info: + description: Builtin Function Test Package diff --git a/pkg/task/testdata/context/expected/bucket.yaml b/pkg/task/testdata/context/expected/bucket.yaml new file mode 100644 index 00000000..5dad26d4 --- /dev/null +++ b/pkg/task/testdata/context/expected/bucket.yaml @@ -0,0 +1,12 @@ +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/context/expected/package-context.yaml b/pkg/task/testdata/context/expected/package-context.yaml new file mode 100644 index 00000000..57b4e523 --- /dev/null +++ b/pkg/task/testdata/context/expected/package-context.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: input + package-path: parent1/parent1.2/parent1.2.3/me diff --git a/pkg/task/testdata/context/input/Kptfile b/pkg/task/testdata/context/input/Kptfile new file mode 100644 index 00000000..1e92a6cf --- /dev/null +++ b/pkg/task/testdata/context/input/Kptfile @@ -0,0 +1,6 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: input +info: + description: Builtin Function Test Package diff --git a/pkg/task/testdata/context/input/bucket.yaml b/pkg/task/testdata/context/input/bucket.yaml new file mode 100644 index 00000000..5dad26d4 --- /dev/null +++ b/pkg/task/testdata/context/input/bucket.yaml @@ -0,0 +1,12 @@ +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: + name: bucket-name + namespace: bucket-namespace + annotations: + cnrm.cloud.google.com/project-id: bucket-project +spec: + storageClass: standard + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/init/testpkg/Kptfile b/pkg/task/testdata/init/testpkg/Kptfile new file mode 100644 index 00000000..7883a649 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/Kptfile @@ -0,0 +1,13 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: testpkg + annotations: + config.kubernetes.io/local-config: "true" +info: + site: http://kpt.dev/testpkg + description: test package + keywords: + - test + - kpt + - pkg diff --git a/pkg/task/testdata/init/testpkg/README.md b/pkg/task/testdata/init/testpkg/README.md new file mode 100644 index 00000000..9a34a509 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/README.md @@ -0,0 +1,21 @@ +# testpkg + +## Description +test package + +## Usage + +### Fetch the package +`kpt pkg get REPO_URI[.git]/PKG_PATH[@VERSION] testpkg` +Details: https://kpt.dev/reference/cli/pkg/get/ + +### View package content +`kpt pkg tree testpkg` +Details: https://kpt.dev/reference/cli/pkg/tree/ + +### Apply the package +``` +kpt live init testpkg +kpt live apply testpkg --reconcile-timeout=2m --output=table +``` +Details: https://kpt.dev/reference/cli/live/ diff --git a/pkg/task/testdata/init/testpkg/package-context.yaml b/pkg/task/testdata/init/testpkg/package-context.yaml new file mode 100644 index 00000000..6c33d9e4 --- /dev/null +++ b/pkg/task/testdata/init/testpkg/package-context.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kptfile.kpt.dev + annotations: + config.kubernetes.io/local-config: "true" +data: + name: example diff --git a/pkg/task/testdata/replace/Kptfile b/pkg/task/testdata/replace/Kptfile new file mode 100644 index 00000000..9529df9a --- /dev/null +++ b/pkg/task/testdata/replace/Kptfile @@ -0,0 +1,23 @@ +# top comment +apiVersion: kpt.dev/v1 +# Kptfile info +info: + # Kptfile description + description: A Google Cloud Storage bucket +# Kptfile kind +kind: Kptfile +# Kptfile metadata +metadata: + annotations: + blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint + name: simple-bucket +# Kptfile pipeline +pipeline: + # Kptfile mutators + mutators: + - configMap: + name: updated-bucket-name + namespace: updated-namespace + project-id: updated-project-id + storage-class: updated-storage-class + image: gcr.io/kpt-fn/apply-setters:v0.2.0 diff --git a/pkg/task/testdata/replace/README.md b/pkg/task/testdata/replace/README.md new file mode 100644 index 00000000..6f0623fa --- /dev/null +++ b/pkg/task/testdata/replace/README.md @@ -0,0 +1 @@ +# replace test \ No newline at end of file diff --git a/pkg/task/testdata/replace/bucket.yaml b/pkg/task/testdata/replace/bucket.yaml new file mode 100644 index 00000000..935e6573 --- /dev/null +++ b/pkg/task/testdata/replace/bucket.yaml @@ -0,0 +1,18 @@ +# top comment +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +# metadata comment +metadata: # kpt-merge: config-control/blueprints-project-bucket + # annotations comment + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} + name: blueprints-project-bucket # kpt-set: ${project-id}-${name} + namespace: config-control # kpt-set: ${namespace} +# spec comment +spec: + storageClass: standard # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + # Versioning is enabled + versioning: + enabled: false diff --git a/pkg/task/testdata/simple-render/expected.txt b/pkg/task/testdata/simple-render/expected.txt new file mode 100644 index 00000000..f625b40c --- /dev/null +++ b/pkg/task/testdata/simple-render/expected.txt @@ -0,0 +1,27 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: # kpt-merge: config-control/blueprints-project-bucket + name: updated-project-id-updated-bucket-name # kpt-set: ${project-id}-${name} + namespace: updated-namespace # kpt-set: ${namespace} + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: updated-project-id # kpt-set: ${project-id} + cnrm.cloud.google.com/blueprint: 'kpt-fn' +spec: + storageClass: updated-storage-class # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + versioning: + enabled: false diff --git a/pkg/task/testdata/simple-render/simple-bucket/Kptfile b/pkg/task/testdata/simple-render/simple-bucket/Kptfile new file mode 100644 index 00000000..7a6d5063 --- /dev/null +++ b/pkg/task/testdata/simple-render/simple-bucket/Kptfile @@ -0,0 +1,29 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: simple-bucket + annotations: + blueprints.cloud.google.com/title: Google Cloud Storage Bucket blueprint +info: + description: A Google Cloud Storage bucket +pipeline: + mutators: + - image: gcr.io/kpt-fn/apply-setters:v0.2.0 + configMap: + name: updated-bucket-name + namespace: updated-namespace + project-id: updated-project-id + storage-class: updated-storage-class diff --git a/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml b/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml new file mode 100644 index 00000000..f80f88b6 --- /dev/null +++ b/pkg/task/testdata/simple-render/simple-bucket/bucket.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 The kpt and Nephio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: storage.cnrm.cloud.google.com/v1beta1 +kind: StorageBucket +metadata: # kpt-merge: config-control/blueprints-project-bucket + name: blueprints-project-bucket # kpt-set: ${project-id}-${name} + namespace: config-control # kpt-set: ${namespace} + annotations: + cnrm.cloud.google.com/force-destroy: "false" + cnrm.cloud.google.com/project-id: blueprints-project # kpt-set: ${project-id} +spec: + storageClass: standard # kpt-set: ${storage-class} + uniformBucketLevelAccess: true + versioning: + enabled: false From 5c782f8ffc7551139f41b7928b44fef0488910e7 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:20:02 +0000 Subject: [PATCH 11/13] Address comments, introduce task interface --- pkg/cache/memory/packagerevision.go | 27 - pkg/cache/memory/util.go | 2 - pkg/engine/engine.go | 5 +- pkg/engine/options.go | 28 +- pkg/repository/fake/packagerevision.go | 7 - pkg/task/builtin.go | 2 +- pkg/task/builtin_test.go | 2 +- pkg/task/clone.go | 4 +- pkg/task/clone_test.go | 4 +- pkg/task/edit.go | 2 +- pkg/task/edit_test.go | 2 +- pkg/task/eval.go | 2 +- pkg/task/generictaskhandler.go | 694 +++++++++++++++++++++++++ pkg/task/init.go | 2 +- pkg/task/init_test.go | 2 +- pkg/task/patchgen.go | 2 +- pkg/task/render.go | 2 +- pkg/task/render_test.go | 2 +- pkg/task/replace_test.go | 2 +- pkg/task/taskhandler.go | 654 +---------------------- 20 files changed, 742 insertions(+), 705 deletions(-) create mode 100644 pkg/task/generictaskhandler.go diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index aff6a6ba..0aed7cba 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -58,30 +58,3 @@ func (c *cachedPackageRevision) GetPackageRevision(ctx context.Context) (*api.Pa return apiPR, nil } - -func (c *cachedPackageRevision) GetCachedPackageRevision(ctx context.Context) (*api.PackageRevision, error) { - repoPkgRev, err := c.GetPackageRevision(ctx) - if err != nil { - return nil, err - } - var isLatest bool - if val, found := repoPkgRev.Labels[api.LatestPackageRevisionKey]; found && val == api.LatestPackageRevisionValue { - isLatest = true - } - repoPkgRev.Labels = c.GetMeta().Labels - if isLatest { - // copy the labels in case the cached object is being read by another go routine - labels := make(map[string]string, len(repoPkgRev.Labels)) - for k, v := range repoPkgRev.Labels { - labels[k] = v - } - labels[api.LatestPackageRevisionKey] = api.LatestPackageRevisionValue - repoPkgRev.Labels = labels - } - repoPkgRev.Annotations = c.GetMeta().Annotations - repoPkgRev.Finalizers = c.GetMeta().Finalizers - repoPkgRev.OwnerReferences = c.GetMeta().OwnerReferences - repoPkgRev.DeletionTimestamp = c.GetMeta().DeletionTimestamp - - return repoPkgRev, nil -} diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index 5f998a72..cdb3ca1c 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -62,8 +62,6 @@ func identifyLatestRevisions(result map[repository.PackageRevisionKey]*cachedPac for _, v := range latest { v.isLatestRevision = true } - - // If the } func toPackageRevisionSlice(cached map[repository.PackageRevisionKey]*cachedPackageRevision, filter repository.ListPackageRevisionFilter) []repository.PackageRevision { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 84d1f14f..6893e579 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -59,7 +59,10 @@ type CaDEngine interface { } func NewCaDEngine(opts ...EngineOption) (CaDEngine, error) { - engine := &cadEngine{} + engine := &cadEngine{ + taskHandler: task.GetDefaultTaskHandler(), + } + for _, opt := range opts { if err := opt.apply(engine); err != nil { return nil, err diff --git a/pkg/engine/options.go b/pkg/engine/options.go index f8b70dec..72997f67 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -34,7 +34,7 @@ type EngineOptionFunc func(engine *cadEngine) error var _ EngineOption = EngineOptionFunc(nil) func (f EngineOptionFunc) apply(engine *cadEngine) error { - engine.taskHandler.RepoOpener = engine + engine.taskHandler.SetRepoOpener(engine) return f(engine) } @@ -48,12 +48,12 @@ func WithCache(cache cache.Cache) EngineOption { func WithBuiltinFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { runtime := newBuiltinRuntime() - if engine.taskHandler.Runtime == nil { - engine.taskHandler.Runtime = runtime - } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.GetRuntime() == nil { + engine.taskHandler.SetRuntime(runtime) + } else if mr, ok := engine.taskHandler.GetRuntime().(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) + engine.taskHandler.SetRuntime(fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.GetRuntime(), runtime})) } return nil }) @@ -65,12 +65,12 @@ func WithGRPCFunctionRuntime(address string, maxGrpcMessageSize int) EngineOptio if err != nil { return fmt.Errorf("failed to create function runtime: %w", err) } - if engine.taskHandler.Runtime == nil { - engine.taskHandler.Runtime = runtime - } else if mr, ok := engine.taskHandler.Runtime.(*fn.MultiRuntime); ok { + if engine.taskHandler.GetRuntime() == nil { + engine.taskHandler.SetRuntime(runtime) + } else if mr, ok := engine.taskHandler.GetRuntime().(*fn.MultiRuntime); ok { mr.Add(runtime) } else { - engine.taskHandler.Runtime = fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.Runtime, runtime}) + engine.taskHandler.SetRuntime(fn.NewMultiRuntime([]fn.FunctionRuntime{engine.taskHandler.GetRuntime(), runtime})) } return nil }) @@ -78,14 +78,14 @@ func WithGRPCFunctionRuntime(address string, maxGrpcMessageSize int) EngineOptio func WithFunctionRuntime(runtime fn.FunctionRuntime) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.Runtime = runtime + engine.taskHandler.SetRuntime(runtime) return nil }) } func WithSimpleFunctionRuntime() EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.Runtime = kpt.NewSimpleFunctionRuntime() + engine.taskHandler.SetRuntime(kpt.NewSimpleFunctionRuntime()) return nil }) } @@ -96,21 +96,21 @@ func WithRunnerOptions(options fnruntime.RunnerOptions) EngineOption { func WithRunnerOptionsResolver(fn func(namespace string) fnruntime.RunnerOptions) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.RunnerOptionsResolver = fn + engine.taskHandler.SetRunnerOptionsResolver(fn) return nil }) } func WithCredentialResolver(resolver repository.CredentialResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.CredentialResolver = resolver + engine.taskHandler.SetCredentialResolver(resolver) return nil }) } func WithReferenceResolver(resolver repository.ReferenceResolver) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - engine.taskHandler.ReferenceResolver = resolver + engine.taskHandler.SetReferenceResolver(resolver) return nil }) } diff --git a/pkg/repository/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go index 6fb1fcba..187b6f0b 100644 --- a/pkg/repository/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -97,10 +97,3 @@ func (f *FakePackageRevision) GetMeta() meta.PackageRevisionMeta { func (f *FakePackageRevision) SetMeta(meta.PackageRevisionMeta) { } - -func (f *FakePackageRevision) IsLatest() bool { - return false -} - -func (f *FakePackageRevision) SetLatest(latest bool) { -} diff --git a/pkg/task/builtin.go b/pkg/task/builtin.go index 8f93daa1..d594309e 100644 --- a/pkg/task/builtin.go +++ b/pkg/task/builtin.go @@ -46,7 +46,7 @@ func newPackageContextGeneratorMutation(packageConfig *builtins.PackageConfig) ( var _ mutation = &builtinEvalMutation{} -func (m *builtinEvalMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *builtinEvalMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ff := &runtimeutil.FunctionFilter{ Run: m.runner.Run, Results: &yaml.RNode{}, diff --git a/pkg/task/builtin_test.go b/pkg/task/builtin_test.go index 29dd5181..f6725ac1 100644 --- a/pkg/task/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -45,7 +45,7 @@ func TestPackageContext(t *testing.T) { t.Fatalf("Failed to get builtin function mutation: %v", err) } - got, _, err := m.Apply(context.Background(), input) + got, _, err := m.apply(context.Background(), input) if err != nil { t.Fatalf("Failed to apply builtin function mutation: %v", err) } diff --git a/pkg/task/clone.go b/pkg/task/clone.go index 6bd35c00..17928f5a 100644 --- a/pkg/task/clone.go +++ b/pkg/task/clone.go @@ -48,7 +48,7 @@ type clonePackageMutation struct { packageConfig *builtins.PackageConfig } -func (m *clonePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *clonePackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "clonePackageMutation::Apply", trace.WithAttributes()) defer span.End() @@ -83,7 +83,7 @@ func (m *clonePackageMutation) Apply(ctx context.Context, resources repository.P if err != nil { return repository.PackageResources{}, nil, err } - cloned, _, err = genPkgContextMutation.Apply(ctx, cloned) + cloned, _, err = genPkgContextMutation.apply(ctx, cloned) if err != nil { return repository.PackageResources{}, nil, fmt.Errorf("failed to generate deployment context %w", err) } diff --git a/pkg/task/clone_test.go b/pkg/task/clone_test.go index 4e773353..8ed8b644 100644 --- a/pkg/task/clone_test.go +++ b/pkg/task/clone_test.go @@ -250,14 +250,14 @@ func TestCloneGitBasicAuth(t *testing.T) { }, } - _, _, err = cpm.Apply(context.Background(), repository.PackageResources{}) + _, _, err = cpm.apply(context.Background(), repository.PackageResources{}) if err == nil { t.Errorf("Expected error (unauthorized); got none") } cpm.credentialResolver = auth - r, _, err := cpm.Apply(context.Background(), repository.PackageResources{}) + r, _, err := cpm.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("task apply failed: %v", err) } diff --git a/pkg/task/edit.go b/pkg/task/edit.go index f6cd645d..59990c2b 100644 --- a/pkg/task/edit.go +++ b/pkg/task/edit.go @@ -34,7 +34,7 @@ type editPackageMutation struct { var _ mutation = &editPackageMutation{} -func (m *editPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *editPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "editPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/edit_test.go b/pkg/task/edit_test.go index 1c48d15d..0ddcf9b3 100644 --- a/pkg/task/edit_test.go +++ b/pkg/task/edit_test.go @@ -86,7 +86,7 @@ info: repoOpener: repoOpener, } - res, _, err := epm.Apply(context.Background(), repository.PackageResources{}) + res, _, err := epm.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("task apply failed: %v", err) } diff --git a/pkg/task/eval.go b/pkg/task/eval.go index 3bbf68b3..a0e79db6 100644 --- a/pkg/task/eval.go +++ b/pkg/task/eval.go @@ -35,7 +35,7 @@ type evalFunctionMutation struct { task *api.Task } -func (m *evalFunctionMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *evalFunctionMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "evalFunctionMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/generictaskhandler.go b/pkg/task/generictaskhandler.go new file mode 100644 index 00000000..abb2cdd4 --- /dev/null +++ b/pkg/task/generictaskhandler.go @@ -0,0 +1,694 @@ +// Copyright 2022,2024 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package task + +import ( + "bytes" + "context" + "fmt" + + api "github.com/nephio-project/porch/api/porch/v1alpha1" + configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/internal/kpt/builtins" + "github.com/nephio-project/porch/internal/kpt/fnruntime" + "github.com/nephio-project/porch/pkg/kpt" + kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" + "github.com/nephio-project/porch/pkg/kpt/fn" + "github.com/nephio-project/porch/pkg/repository" + "go.opentelemetry.io/otel/trace" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/kustomize/kyaml/comments" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var _ TaskHandler = &genericTaskHandler{} + +type genericTaskHandler struct { + runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions + runtime fn.FunctionRuntime + repoOpener repository.RepositoryOpener + credentialResolver repository.CredentialResolver + referenceResolver repository.ReferenceResolver +} + +func (th *genericTaskHandler) GetRuntime() fn.FunctionRuntime { + return th.runtime +} + +func (th *genericTaskHandler) SetRunnerOptionsResolver(runnerOptionsResolver func(namespace string) fnruntime.RunnerOptions) { + th.runnerOptionsResolver = runnerOptionsResolver +} + +func (th *genericTaskHandler) SetRuntime(runtime fn.FunctionRuntime) { + th.runtime = runtime +} + +func (th *genericTaskHandler) SetRepoOpener(repoOpener repository.RepositoryOpener) { + th.repoOpener = repoOpener +} + +func (th *genericTaskHandler) SetCredentialResolver(credentialResolver repository.CredentialResolver) { + th.credentialResolver = credentialResolver +} + +func (th *genericTaskHandler) SetReferenceResolver(referenceResolver repository.ReferenceResolver) { + th.referenceResolver = referenceResolver +} + +func (th *genericTaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { + var mutations []mutation + + // Unless first task is Init or Clone, insert Init to create an empty package. + tasks := obj.Spec.Tasks + if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { + mutations = append(mutations, &initPackageMutation{ + name: obj.Spec.PackageName, + task: &api.Task{ + Init: &api.PackageInitTaskSpec{ + Subpackage: "", + Description: fmt.Sprintf("%s description", obj.Spec.PackageName), + }, + }, + }) + } + + for i := range tasks { + task := &tasks[i] + mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) + if err != nil { + return err + } + mutations = append(mutations, mutation) + } + + // Render package after creation. + mutations = th.conditionalAddRender(obj, mutations) + + baseResources := repository.PackageResources{} + if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { + return err + } + + return nil +} + +func (th *genericTaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { + ctx, span := tracer.Start(ctx, "genericTaskHandler::DoPRMutations", trace.WithAttributes()) + defer span.End() + + var mutations []mutation + if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { + return fmt.Errorf("removing tasks is not yet supported") + } + for i := range oldObj.Spec.Tasks { + oldTask := &oldObj.Spec.Tasks[i] + newTask := &newObj.Spec.Tasks[i] + if oldTask.Type != newTask.Type { + return fmt.Errorf("changing task types is not yet supported") + } + } + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { + if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { + return fmt.Errorf("can only append one task at a time") + } + + newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] + if newTask.Type != api.TaskTypeUpdate { + return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) + } + if newTask.Update == nil { + return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) + } + + cloneTask := findCloneTask(oldObj) + if cloneTask == nil { + return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) + } + + mutation := &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: &newTask, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + namespace: namespace, + pkgName: oldObj.GetName(), + } + mutations = append(mutations, mutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // If any of the fields in the API that are projections from the Kptfile + // must be updated in the Kptfile as well. + kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) + if err != nil { + return err + } + if created { + kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) + if err != nil { + return err + } + mutations = append(mutations, kfPatchMutation) + } + + // Re-render if we are making changes. + mutations = th.conditionalAddRender(newObj, mutations) + + // TODO: Handle the case if alongside lifecycle change, tasks are changed too. + // Update package contents only if the package is in draft state + if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { + apiResources, err := repoPR.GetResources(ctx) + if err != nil { + return fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: apiResources.Spec.Resources, + } + + if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { + return err + } + } + + return nil +} + +func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { + for _, tt := range oneOf { + if taskType == tt { + return true + } + } + return false +} + +func (th *genericTaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { + ctx, span := tracer.Start(ctx, "genericTaskHandler::DoPRResourceMutations", trace.WithAttributes()) + defer span.End() + + runnerOptions := th.runnerOptionsResolver(oldRes.GetNamespace()) + + mutations := []mutation{ + &mutationReplaceResources{ + newResources: newRes, + oldResources: oldRes, + }, + } + prevResources, err := pr2Update.GetResources(ctx) + if err != nil { + return nil, fmt.Errorf("cannot get package resources: %w", err) + } + resources := repository.PackageResources{ + Contents: prevResources.Spec.Resources, + } + + appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) + if err != nil { + return nil, err + } + + var renderStatus *api.RenderStatus + if len(appliedResources.Contents) > 0 { + // render the package + // Render failure will not fail the overall API operation. + // The render error and result is captured as part of renderStatus above + // and is returned in packageresourceresources API's status field. We continue with + // saving the non-rendered resources to avoid losing user's changes. + // and supress this err. + _, renderStatus, _ = applyResourceMutations(ctx, + draft, + appliedResources, + []mutation{&renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }}) + } else { + renderStatus = nil + } + + return renderStatus, nil +} + +func (th *genericTaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { + switch task.Type { + case api.TaskTypeInit: + if task.Init == nil { + return nil, fmt.Errorf("init not set for task of type %q", task.Type) + } + return &initPackageMutation{ + name: obj.Spec.PackageName, + task: task, + }, nil + case api.TaskTypeClone: + if task.Clone == nil { + return nil, fmt.Errorf("clone not set for task of type %q", task.Type) + } + return &clonePackageMutation{ + task: task, + namespace: obj.Namespace, + name: obj.Spec.PackageName, + isDeployment: isDeployment, + repoOpener: th.repoOpener, + credentialResolver: th.credentialResolver, + referenceResolver: th.referenceResolver, + packageConfig: packageConfig, + }, nil + + case api.TaskTypeUpdate: + if task.Update == nil { + return nil, fmt.Errorf("update not set for task of type %q", task.Type) + } + cloneTask := findCloneTask(obj) + if cloneTask == nil { + return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) + } + return &updatePackageMutation{ + cloneTask: cloneTask, + updateTask: task, + namespace: obj.Namespace, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + pkgName: obj.Spec.PackageName, + }, nil + + case api.TaskTypePatch: + return buildPatchMutation(ctx, task) + + case api.TaskTypeEdit: + if task.Edit == nil { + return nil, fmt.Errorf("edit not set for task of type %q", task.Type) + } + return &editPackageMutation{ + task: task, + namespace: obj.Namespace, + packageName: obj.Spec.PackageName, + repositoryName: obj.Spec.RepositoryName, + repoOpener: th.repoOpener, + referenceResolver: th.referenceResolver, + }, nil + + case api.TaskTypeEval: + if task.Eval == nil { + return nil, fmt.Errorf("eval not set for task of type %q", task.Type) + } + // TODO: We should find a different way to do this. Probably a separate + // task for render. + if task.Eval.Image == "render" { + runnerOptions := th.runnerOptionsResolver(obj.Namespace) + return &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }, nil + } else { + runnerOptions := th.runnerOptionsResolver(obj.Namespace) + return &evalFunctionMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + task: task, + }, nil + } + + default: + return nil, fmt.Errorf("task of type %q not supported", task.Type) + } +} + +func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { + kf, err := oldPackage.GetKptfile(ctx) + if err != nil { + return nil, false, err + } + + var orgKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + orgKfString = buf.String() + } + + var readinessGates []kptfile.ReadinessGate + for _, rg := range newObj.Spec.ReadinessGates { + readinessGates = append(readinessGates, kptfile.ReadinessGate{ + ConditionType: rg.ConditionType, + }) + } + + var conditions []kptfile.Condition + for _, c := range newObj.Status.Conditions { + conditions = append(conditions, kptfile.Condition{ + Type: c.Type, + Status: convertStatusToKptfile(c.Status), + Reason: c.Reason, + Message: c.Message, + }) + } + + if kf.Info == nil && len(readinessGates) > 0 { + kf.Info = &kptfile.PackageInfo{} + } + if len(readinessGates) > 0 { + kf.Info.ReadinessGates = readinessGates + } + + if kf.Status == nil && len(conditions) > 0 { + kf.Status = &kptfile.Status{} + } + if len(conditions) > 0 { + kf.Status.Conditions = conditions + } + + var newKfString string + { + var buf bytes.Buffer + d := yaml.NewEncoder(&buf) + if err := d.Encode(kf); err != nil { + return nil, false, err + } + newKfString = buf.String() + } + patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) + if err != nil { + return nil, false, err + } + // If patch is empty, don't create a Task. + if patchSpec.Contents == "" { + return nil, false, nil + } + + return &api.Task{ + Type: api.TaskTypePatch, + Patch: &api.PackagePatchTaskSpec{ + Patches: []api.PatchSpec{ + patchSpec, + }, + }, + }, true, nil +} + +func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { + switch s { + case api.ConditionTrue: + return kptfile.ConditionTrue + case api.ConditionFalse: + return kptfile.ConditionFalse + case api.ConditionUnknown: + return kptfile.ConditionUnknown + default: + panic(fmt.Errorf("unknown condition status: %v", s)) + } +} + +// conditionalAddRender adds a render mutation to the end of the mutations slice if the last +// entry is not already a render mutation. +func (th *genericTaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { + if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { + return mutations + } + + runnerOptions := th.runnerOptionsResolver(subject.GetNamespace()) + + return append(mutations, &renderPackageMutation{ + runnerOptions: runnerOptions, + runtime: th.runtime, + }) +} + +func isRenderMutation(m mutation) bool { + _, isRender := m.(*renderPackageMutation) + return isRender +} + +// applyResourceMutations mutates the resources and returns the most recent renderResult. +func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { + var lastApplied mutation + for _, m := range mutations { + updatedResources, taskResult, err := m.apply(ctx, baseResources) + if taskResult == nil && err == nil { + // a nil taskResult means nothing changed + continue + } + + var task *api.Task + if taskResult != nil { + task = taskResult.Task + } + if taskResult != nil && task.Type == api.TaskTypeEval { + renderStatus = taskResult.RenderStatus + } + if err != nil { + return updatedResources, renderStatus, err + } + + // if the last applied mutation was a render mutation, and so is this one, skip it + if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { + continue + } + lastApplied = m + + if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ + Spec: api.PackageRevisionResourcesSpec{ + Resources: updatedResources.Contents, + }, + }, task); err != nil { + return updatedResources, renderStatus, err + } + baseResources = updatedResources + applied = updatedResources + } + + return applied, renderStatus, nil +} + +type updatePackageMutation struct { + cloneTask *api.Task + updateTask *api.Task + repoOpener repository.RepositoryOpener + referenceResolver repository.ReferenceResolver + namespace string + pkgName string +} + +func (m *updatePackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) + defer span.End() + + currUpstreamPkgRef, err := m.currUpstream() + if err != nil { + return repository.PackageResources{}, nil, err + } + + targetUpstream := m.updateTask.Update.Upstream + if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { + return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") + } + + originalResources, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", + m.pkgName, *currUpstreamPkgRef) + } + + upstreamRevision, err := (&repository.PackageFetcher{ + RepoOpener: m.repoOpener, + ReferenceResolver: m.referenceResolver, + }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) + } + upstreamResources, err := upstreamRevision.GetResources(ctx) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) + } + + klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", + m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) + + // May be have packageUpdater part of the Porch core to make it easy for testing ? + updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, + resources, + repository.PackageResources{ + Contents: originalResources.Spec.Resources, + }, + repository.PackageResources{ + Contents: upstreamResources.Spec.Resources, + }) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) + } + + newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) + } + if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) + } + + // ensure merge-key comment is added to newly added resources. + result, err := ensureMergeKey(ctx, updatedResources) + if err != nil { + klog.Infof("failed to add merge key comments: %v", err) + } + return result, &api.TaskResult{Task: m.updateTask}, nil +} + +// Currently assumption is that downstream packages will be forked from a porch package. +// As per current implementation, upstream package ref is stored in a new update task but this may +// change so the logic of figuring out current upstream will live in this function. +func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { + if m.cloneTask == nil || m.cloneTask.Clone == nil { + return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) + } + upstream := m.cloneTask.Clone.Upstream + if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { + return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) + } + return upstream.UpstreamRef, nil +} + +func findCloneTask(pr *api.PackageRevision) *api.Task { + if len(pr.Spec.Tasks) == 0 { + return nil + } + firstTask := pr.Spec.Tasks[0] + if firstTask.Type == api.TaskTypeClone { + return &firstTask + } + return nil +} + +type mutationReplaceResources struct { + newResources *api.PackageRevisionResources + oldResources *api.PackageRevisionResources +} + +func (m *mutationReplaceResources) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { + _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) + defer span.End() + + patch := &api.PackagePatchTaskSpec{} + + old := resources.Contents + new, err := healConfig(old, m.newResources.Spec.Resources) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) + } + + for k, newV := range new { + oldV, ok := old[k] + // New config or changed config + if !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeCreateFile, + Contents: newV, + } + patch.Patches = append(patch.Patches, patchSpec) + } else if newV != oldV { + patchSpec, err := GeneratePatch(k, oldV, newV) + if err != nil { + return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) + } + if patchSpec.Contents == "" { + continue + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + for k := range old { + // Deleted config + if _, ok := new[k]; !ok { + patchSpec := api.PatchSpec{ + File: k, + PatchType: api.PatchTypeDeleteFile, + } + patch.Patches = append(patch.Patches, patchSpec) + } + } + // If patch is empty, don't create a Task. + var taskResult *api.TaskResult + if len(patch.Patches) > 0 { + taskResult = &api.TaskResult{ + Task: &api.Task{ + Type: api.TaskTypePatch, + Patch: patch, + }, + } + } + return repository.PackageResources{Contents: new}, taskResult, nil +} + +func healConfig(old, new map[string]string) (map[string]string, error) { + // Copy comments from old config to new + oldResources, err := (&packageReader{ + input: repository.PackageResources{Contents: old}, + extra: map[string]string{}, + }).Read() + if err != nil { + return nil, fmt.Errorf("failed to read old packge resources: %w", err) + } + + var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { + for _, n := range r { + for _, original := range oldResources { + if n.GetNamespace() == original.GetNamespace() && + n.GetName() == original.GetName() && + n.GetApiVersion() == original.GetApiVersion() && + n.GetKind() == original.GetKind() { + comments.CopyComments(original, n) + } + } + } + return r, nil + } + + out := &packageWriter{ + output: repository.PackageResources{ + Contents: map[string]string{}, + }, + } + + extra := map[string]string{} + + if err := (kio.Pipeline{ + Inputs: []kio.Reader{&packageReader{ + input: repository.PackageResources{Contents: new}, + extra: extra, + }}, + Filters: []kio.Filter{filter}, + Outputs: []kio.Writer{out}, + ContinueOnEmptyResult: true, + }).Execute(); err != nil { + return nil, err + } + + healed := out.output.Contents + + for k, v := range extra { + healed[k] = v + } + + return healed, nil +} diff --git a/pkg/task/init.go b/pkg/task/init.go index a279f732..5e113499 100644 --- a/pkg/task/init.go +++ b/pkg/task/init.go @@ -35,7 +35,7 @@ type initPackageMutation struct { var _ mutation = &initPackageMutation{} -func (m *initPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *initPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "initPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/init_test.go b/pkg/task/init_test.go index 0593f1aa..93c0c634 100644 --- a/pkg/task/init_test.go +++ b/pkg/task/init_test.go @@ -42,7 +42,7 @@ func TestInit(t *testing.T) { t.Fatalf("Failed to find testdata: %v", err) } - initializedPkg, _, err := init.Apply(context.Background(), repository.PackageResources{}) + initializedPkg, _, err := init.apply(context.Background(), repository.PackageResources{}) if err != nil { t.Errorf("package init failed: %v", err) } diff --git a/pkg/task/patchgen.go b/pkg/task/patchgen.go index e27b14ed..6cb5b73a 100644 --- a/pkg/task/patchgen.go +++ b/pkg/task/patchgen.go @@ -52,7 +52,7 @@ type applyPatchMutation struct { var _ mutation = &applyPatchMutation{} -func (m *applyPatchMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *applyPatchMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { _, span := tracer.Start(ctx, "applyPatchMutation:::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/render.go b/pkg/task/render.go index 31d5744b..dc91df2c 100644 --- a/pkg/task/render.go +++ b/pkg/task/render.go @@ -39,7 +39,7 @@ type renderPackageMutation struct { var _ mutation = &renderPackageMutation{} -func (m *renderPackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { +func (m *renderPackageMutation) apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { ctx, span := tracer.Start(ctx, "renderPackageMutation::Apply", trace.WithAttributes()) defer span.End() diff --git a/pkg/task/render_test.go b/pkg/task/render_test.go index bf24b6b1..c2a16268 100644 --- a/pkg/task/render_test.go +++ b/pkg/task/render_test.go @@ -60,7 +60,7 @@ func TestRender(t *testing.T) { t.Fatalf("Failed to read package: %v", err) } - rendered, _, err := render.Apply(context.Background(), w.output) + rendered, _, err := render.apply(context.Background(), w.output) if err != nil { t.Errorf("package render failed: %v", err) } diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index fc29a4cc..571f361d 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -46,7 +46,7 @@ func TestReplaceResources(t *testing.T) { }, } - output, _, err := replace.Apply(ctx, input) + output, _, err := replace.apply(ctx, input) if err != nil { t.Fatalf("mutationReplaceResources.Apply failed: %v", err) } diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go index 5ea48726..030e5bef 100644 --- a/pkg/task/taskhandler.go +++ b/pkg/task/taskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2022,2024 The kpt and Nephio Authors +// Copyright 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,661 +15,37 @@ package task import ( - "bytes" "context" - "fmt" api "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" "github.com/nephio-project/porch/internal/kpt/builtins" "github.com/nephio-project/porch/internal/kpt/fnruntime" - "github.com/nephio-project/porch/pkg/kpt" - kptfile "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1" "github.com/nephio-project/porch/pkg/kpt/fn" "github.com/nephio-project/porch/pkg/repository" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" - "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/kustomize/kyaml/comments" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" ) var tracer = otel.Tracer("task") -type TaskHandler struct { - RunnerOptionsResolver func(namespace string) fnruntime.RunnerOptions - Runtime fn.FunctionRuntime - RepoOpener repository.RepositoryOpener - CredentialResolver repository.CredentialResolver - ReferenceResolver repository.ReferenceResolver -} - -type mutation interface { - Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) -} - -func taskTypeOneOf(taskType api.TaskType, oneOf ...api.TaskType) bool { - for _, tt := range oneOf { - if taskType == tt { - return true - } - } - return false -} - -func (th *TaskHandler) ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error { - var mutations []mutation - - // Unless first task is Init or Clone, insert Init to create an empty package. - tasks := obj.Spec.Tasks - if len(tasks) == 0 || !taskTypeOneOf(tasks[0].Type, api.TaskTypeInit, api.TaskTypeClone, api.TaskTypeEdit) { - mutations = append(mutations, &initPackageMutation{ - name: obj.Spec.PackageName, - task: &api.Task{ - Init: &api.PackageInitTaskSpec{ - Subpackage: "", - Description: fmt.Sprintf("%s description", obj.Spec.PackageName), - }, - }, - }) - } - - for i := range tasks { - task := &tasks[i] - mutation, err := th.mapTaskToMutation(ctx, obj, task, repositoryObj.Spec.Deployment, packageConfig) - if err != nil { - return err - } - mutations = append(mutations, mutation) - } - - // Render package after creation. - mutations = th.conditionalAddRender(obj, mutations) - - baseResources := repository.PackageResources{} - if _, _, err := applyResourceMutations(ctx, draft, baseResources, mutations); err != nil { - return err - } - - return nil -} - -func (th *TaskHandler) mapTaskToMutation(ctx context.Context, obj *api.PackageRevision, task *api.Task, isDeployment bool, packageConfig *builtins.PackageConfig) (mutation, error) { - switch task.Type { - case api.TaskTypeInit: - if task.Init == nil { - return nil, fmt.Errorf("init not set for task of type %q", task.Type) - } - return &initPackageMutation{ - name: obj.Spec.PackageName, - task: task, - }, nil - case api.TaskTypeClone: - if task.Clone == nil { - return nil, fmt.Errorf("clone not set for task of type %q", task.Type) - } - return &clonePackageMutation{ - task: task, - namespace: obj.Namespace, - name: obj.Spec.PackageName, - isDeployment: isDeployment, - repoOpener: th.RepoOpener, - credentialResolver: th.CredentialResolver, - referenceResolver: th.ReferenceResolver, - packageConfig: packageConfig, - }, nil - - case api.TaskTypeUpdate: - if task.Update == nil { - return nil, fmt.Errorf("update not set for task of type %q", task.Type) - } - cloneTask := findCloneTask(obj) - if cloneTask == nil { - return nil, fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", obj.Spec.PackageName) - } - return &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: task, - namespace: obj.Namespace, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - pkgName: obj.Spec.PackageName, - }, nil - - case api.TaskTypePatch: - return buildPatchMutation(ctx, task) - - case api.TaskTypeEdit: - if task.Edit == nil { - return nil, fmt.Errorf("edit not set for task of type %q", task.Type) - } - return &editPackageMutation{ - task: task, - namespace: obj.Namespace, - packageName: obj.Spec.PackageName, - repositoryName: obj.Spec.RepositoryName, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - }, nil - - case api.TaskTypeEval: - if task.Eval == nil { - return nil, fmt.Errorf("eval not set for task of type %q", task.Type) - } - // TODO: We should find a different way to do this. Probably a separate - // task for render. - if task.Eval.Image == "render" { - runnerOptions := th.RunnerOptionsResolver(obj.Namespace) - return &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }, nil - } else { - runnerOptions := th.RunnerOptionsResolver(obj.Namespace) - return &evalFunctionMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - task: task, - }, nil - } - - default: - return nil, fmt.Errorf("task of type %q not supported", task.Type) - } -} - -func createKptfilePatchTask(ctx context.Context, oldPackage repository.PackageRevision, newObj *api.PackageRevision) (*api.Task, bool, error) { - kf, err := oldPackage.GetKptfile(ctx) - if err != nil { - return nil, false, err - } - - var orgKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - orgKfString = buf.String() - } - - var readinessGates []kptfile.ReadinessGate - for _, rg := range newObj.Spec.ReadinessGates { - readinessGates = append(readinessGates, kptfile.ReadinessGate{ - ConditionType: rg.ConditionType, - }) - } - - var conditions []kptfile.Condition - for _, c := range newObj.Status.Conditions { - conditions = append(conditions, kptfile.Condition{ - Type: c.Type, - Status: convertStatusToKptfile(c.Status), - Reason: c.Reason, - Message: c.Message, - }) - } - - if kf.Info == nil && len(readinessGates) > 0 { - kf.Info = &kptfile.PackageInfo{} - } - if len(readinessGates) > 0 { - kf.Info.ReadinessGates = readinessGates - } - - if kf.Status == nil && len(conditions) > 0 { - kf.Status = &kptfile.Status{} - } - if len(conditions) > 0 { - kf.Status.Conditions = conditions - } - - var newKfString string - { - var buf bytes.Buffer - d := yaml.NewEncoder(&buf) - if err := d.Encode(kf); err != nil { - return nil, false, err - } - newKfString = buf.String() - } - patchSpec, err := GeneratePatch(kptfile.KptFileName, orgKfString, newKfString) - if err != nil { - return nil, false, err - } - // If patch is empty, don't create a Task. - if patchSpec.Contents == "" { - return nil, false, nil - } - - return &api.Task{ - Type: api.TaskTypePatch, - Patch: &api.PackagePatchTaskSpec{ - Patches: []api.PatchSpec{ - patchSpec, - }, - }, - }, true, nil -} - -func convertStatusToKptfile(s api.ConditionStatus) kptfile.ConditionStatus { - switch s { - case api.ConditionTrue: - return kptfile.ConditionTrue - case api.ConditionFalse: - return kptfile.ConditionFalse - case api.ConditionUnknown: - return kptfile.ConditionUnknown - default: - panic(fmt.Errorf("unknown condition status: %v", s)) - } -} - -// conditionalAddRender adds a render mutation to the end of the mutations slice if the last -// entry is not already a render mutation. -func (th *TaskHandler) conditionalAddRender(subject client.Object, mutations []mutation) []mutation { - if len(mutations) == 0 || isRenderMutation(mutations[len(mutations)-1]) { - return mutations - } - - runnerOptions := th.RunnerOptionsResolver(subject.GetNamespace()) - - return append(mutations, &renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }) -} - -func isRenderMutation(m mutation) bool { - _, isRender := m.(*renderPackageMutation) - return isRender -} - -// applyResourceMutations mutates the resources and returns the most recent renderResult. -func applyResourceMutations(ctx context.Context, draft repository.PackageDraft, baseResources repository.PackageResources, mutations []mutation) (applied repository.PackageResources, renderStatus *api.RenderStatus, err error) { - var lastApplied mutation - for _, m := range mutations { - updatedResources, taskResult, err := m.Apply(ctx, baseResources) - if taskResult == nil && err == nil { - // a nil taskResult means nothing changed - continue - } - - var task *api.Task - if taskResult != nil { - task = taskResult.Task - } - if taskResult != nil && task.Type == api.TaskTypeEval { - renderStatus = taskResult.RenderStatus - } - if err != nil { - return updatedResources, renderStatus, err - } - - // if the last applied mutation was a render mutation, and so is this one, skip it - if lastApplied != nil && isRenderMutation(m) && isRenderMutation(lastApplied) { - continue - } - lastApplied = m - - if err := draft.UpdateResources(ctx, &api.PackageRevisionResources{ - Spec: api.PackageRevisionResourcesSpec{ - Resources: updatedResources.Contents, - }, - }, task); err != nil { - return updatedResources, renderStatus, err - } - baseResources = updatedResources - applied = updatedResources - } - - return applied, renderStatus, nil -} - -type updatePackageMutation struct { - cloneTask *api.Task - updateTask *api.Task - repoOpener repository.RepositoryOpener - referenceResolver repository.ReferenceResolver - namespace string - pkgName string -} - -func (m *updatePackageMutation) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - ctx, span := tracer.Start(ctx, "updatePackageMutation::Apply", trace.WithAttributes()) - defer span.End() - - currUpstreamPkgRef, err := m.currUpstream() - if err != nil { - return repository.PackageResources{}, nil, err - } - - targetUpstream := m.updateTask.Update.Upstream - if targetUpstream.Type == api.RepositoryTypeGit || targetUpstream.Type == api.RepositoryTypeOCI { - return repository.PackageResources{}, nil, fmt.Errorf("update is not supported for non-porch upstream packages") - } - - originalResources, err := (&repository.PackageFetcher{ - RepoOpener: m.repoOpener, - ReferenceResolver: m.referenceResolver, - }).FetchResources(ctx, currUpstreamPkgRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package %s with ref %+v", - m.pkgName, *currUpstreamPkgRef) - } - - upstreamRevision, err := (&repository.PackageFetcher{ - RepoOpener: m.repoOpener, - ReferenceResolver: m.referenceResolver, - }).FetchRevision(ctx, targetUpstream.UpstreamRef, m.namespace) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching revision for target upstream %s", targetUpstream.UpstreamRef.Name) - } - upstreamResources, err := upstreamRevision.GetResources(ctx) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching resources for target upstream %s", targetUpstream.UpstreamRef.Name) - } - - klog.Infof("performing pkg upgrade operation for pkg %s resource counts local[%d] original[%d] upstream[%d]", - m.pkgName, len(resources.Contents), len(originalResources.Spec.Resources), len(upstreamResources.Spec.Resources)) - - // May be have packageUpdater part of the Porch core to make it easy for testing ? - updatedResources, err := (&repository.DefaultPackageUpdater{}).Update(ctx, - resources, - repository.PackageResources{ - Contents: originalResources.Spec.Resources, - }, - repository.PackageResources{ - Contents: upstreamResources.Spec.Resources, - }) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error updating the package to revision %s", targetUpstream.UpstreamRef.Name) - } - - newUpstream, newUpstreamLock, err := upstreamRevision.GetLock() - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error fetching the resources for package revisions %s", targetUpstream.UpstreamRef.Name) - } - if err := kpt.UpdateKptfileUpstream("", updatedResources.Contents, newUpstream, newUpstreamLock); err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to apply upstream lock to package %q: %w", m.pkgName, err) - } - - // ensure merge-key comment is added to newly added resources. - result, err := ensureMergeKey(ctx, updatedResources) - if err != nil { - klog.Infof("failed to add merge key comments: %v", err) - } - return result, &api.TaskResult{Task: m.updateTask}, nil -} - -// Currently assumption is that downstream packages will be forked from a porch package. -// As per current implementation, upstream package ref is stored in a new update task but this may -// change so the logic of figuring out current upstream will live in this function. -func (m *updatePackageMutation) currUpstream() (*api.PackageRevisionRef, error) { - if m.cloneTask == nil || m.cloneTask.Clone == nil { - return nil, fmt.Errorf("package %s does not have original upstream info", m.pkgName) - } - upstream := m.cloneTask.Clone.Upstream - if upstream.Type == api.RepositoryTypeGit || upstream.Type == api.RepositoryTypeOCI { - return nil, fmt.Errorf("upstream package must be porch native package. Found it to be %s", upstream.Type) - } - return upstream.UpstreamRef, nil -} - -func findCloneTask(pr *api.PackageRevision) *api.Task { - if len(pr.Spec.Tasks) == 0 { - return nil - } - firstTask := pr.Spec.Tasks[0] - if firstTask.Type == api.TaskTypeClone { - return &firstTask - } - return nil -} - -type mutationReplaceResources struct { - newResources *api.PackageRevisionResources - oldResources *api.PackageRevisionResources -} - -func (m *mutationReplaceResources) Apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) { - _, span := tracer.Start(ctx, "mutationReplaceResources::Apply", trace.WithAttributes()) - defer span.End() - - patch := &api.PackagePatchTaskSpec{} - - old := resources.Contents - new, err := healConfig(old, m.newResources.Spec.Resources) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("failed to heal resources: %w", err) - } - - for k, newV := range new { - oldV, ok := old[k] - // New config or changed config - if !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeCreateFile, - Contents: newV, - } - patch.Patches = append(patch.Patches, patchSpec) - } else if newV != oldV { - patchSpec, err := GeneratePatch(k, oldV, newV) - if err != nil { - return repository.PackageResources{}, nil, fmt.Errorf("error generating patch: %w", err) - } - if patchSpec.Contents == "" { - continue - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - for k := range old { - // Deleted config - if _, ok := new[k]; !ok { - patchSpec := api.PatchSpec{ - File: k, - PatchType: api.PatchTypeDeleteFile, - } - patch.Patches = append(patch.Patches, patchSpec) - } - } - // If patch is empty, don't create a Task. - var taskResult *api.TaskResult - if len(patch.Patches) > 0 { - taskResult = &api.TaskResult{ - Task: &api.Task{ - Type: api.TaskTypePatch, - Patch: patch, - }, - } - } - return repository.PackageResources{Contents: new}, taskResult, nil -} +type TaskHandler interface { + GetRuntime() fn.FunctionRuntime -func healConfig(old, new map[string]string) (map[string]string, error) { - // Copy comments from old config to new - oldResources, err := (&packageReader{ - input: repository.PackageResources{Contents: old}, - extra: map[string]string{}, - }).Read() - if err != nil { - return nil, fmt.Errorf("failed to read old packge resources: %w", err) - } + SetRunnerOptionsResolver(func(namespace string) fnruntime.RunnerOptions) + SetRuntime(fn.FunctionRuntime) + SetRepoOpener(repository.RepositoryOpener) + SetCredentialResolver(repository.CredentialResolver) + SetReferenceResolver(repository.ReferenceResolver) - var filter kio.FilterFunc = func(r []*yaml.RNode) ([]*yaml.RNode, error) { - for _, n := range r { - for _, original := range oldResources { - if n.GetNamespace() == original.GetNamespace() && - n.GetName() == original.GetName() && - n.GetApiVersion() == original.GetApiVersion() && - n.GetKind() == original.GetKind() { - comments.CopyComments(original, n) - } - } - } - return r, nil - } - - out := &packageWriter{ - output: repository.PackageResources{ - Contents: map[string]string{}, - }, - } - - extra := map[string]string{} - - if err := (kio.Pipeline{ - Inputs: []kio.Reader{&packageReader{ - input: repository.PackageResources{Contents: new}, - extra: extra, - }}, - Filters: []kio.Filter{filter}, - Outputs: []kio.Writer{out}, - ContinueOnEmptyResult: true, - }).Execute(); err != nil { - return nil, err - } - - healed := out.output.Contents - - for k, v := range extra { - healed[k] = v - } - - return healed, nil + ApplyTasks(ctx context.Context, draft repository.PackageDraft, repositoryObj *configapi.Repository, obj *api.PackageRevision, packageConfig *builtins.PackageConfig) error + DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error + DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) } -func (th *TaskHandler) DoPRMutations(ctx context.Context, namespace string, repoPR repository.PackageRevision, oldObj *api.PackageRevision, newObj *api.PackageRevision, draft repository.PackageDraft) error { - ctx, span := tracer.Start(ctx, "TaskHandler::DoPRMutations", trace.WithAttributes()) - defer span.End() - - var mutations []mutation - if len(oldObj.Spec.Tasks) > len(newObj.Spec.Tasks) { - return fmt.Errorf("removing tasks is not yet supported") - } - for i := range oldObj.Spec.Tasks { - oldTask := &oldObj.Spec.Tasks[i] - newTask := &newObj.Spec.Tasks[i] - if oldTask.Type != newTask.Type { - return fmt.Errorf("changing task types is not yet supported") - } - } - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks) { - if len(newObj.Spec.Tasks) > len(oldObj.Spec.Tasks)+1 { - return fmt.Errorf("can only append one task at a time") - } - - newTask := newObj.Spec.Tasks[len(newObj.Spec.Tasks)-1] - if newTask.Type != api.TaskTypeUpdate { - return fmt.Errorf("appended task is type %q, must be type %q", newTask.Type, api.TaskTypeUpdate) - } - if newTask.Update == nil { - return fmt.Errorf("update not set for updateTask of type %q", newTask.Type) - } - - cloneTask := findCloneTask(oldObj) - if cloneTask == nil { - return fmt.Errorf("upstream source not found for package rev %q; only cloned packages can be updated", oldObj.Spec.PackageName) - } - - mutation := &updatePackageMutation{ - cloneTask: cloneTask, - updateTask: &newTask, - repoOpener: th.RepoOpener, - referenceResolver: th.ReferenceResolver, - namespace: namespace, - pkgName: oldObj.GetName(), - } - mutations = append(mutations, mutation) - } - - // Re-render if we are making changes. - mutations = th.conditionalAddRender(newObj, mutations) - - // If any of the fields in the API that are projections from the Kptfile - // must be updated in the Kptfile as well. - kfPatchTask, created, err := createKptfilePatchTask(ctx, repoPR, newObj) - if err != nil { - return err - } - if created { - kfPatchMutation, err := buildPatchMutation(ctx, kfPatchTask) - if err != nil { - return err - } - mutations = append(mutations, kfPatchMutation) - } - - // Re-render if we are making changes. - mutations = th.conditionalAddRender(newObj, mutations) - - // TODO: Handle the case if alongside lifecycle change, tasks are changed too. - // Update package contents only if the package is in draft state - if oldObj.Spec.Lifecycle == api.PackageRevisionLifecycleDraft { - apiResources, err := repoPR.GetResources(ctx) - if err != nil { - return fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: apiResources.Spec.Resources, - } - - if _, _, err := applyResourceMutations(ctx, draft, resources, mutations); err != nil { - return err - } - } - - return nil +type mutation interface { + apply(ctx context.Context, resources repository.PackageResources) (repository.PackageResources, *api.TaskResult, error) } -func (th *TaskHandler) DoPRResourceMutations(ctx context.Context, pr2Update repository.PackageRevision, draft repository.PackageDraft, oldRes, newRes *api.PackageRevisionResources) (*api.RenderStatus, error) { - ctx, span := tracer.Start(ctx, "TaskHandler::DoPRResourceMutations", trace.WithAttributes()) - defer span.End() - - runnerOptions := th.RunnerOptionsResolver(oldRes.GetNamespace()) - - mutations := []mutation{ - &mutationReplaceResources{ - newResources: newRes, - oldResources: oldRes, - }, - } - prevResources, err := pr2Update.GetResources(ctx) - if err != nil { - return nil, fmt.Errorf("cannot get package resources: %w", err) - } - resources := repository.PackageResources{ - Contents: prevResources.Spec.Resources, - } - - appliedResources, _, err := applyResourceMutations(ctx, draft, resources, mutations) - if err != nil { - return nil, err - } - - var renderStatus *api.RenderStatus - if len(appliedResources.Contents) > 0 { - // render the package - // Render failure will not fail the overall API operation. - // The render error and result is captured as part of renderStatus above - // and is returned in packageresourceresources API's status field. We continue with - // saving the non-rendered resources to avoid losing user's changes. - // and supress this err. - _, renderStatus, _ = applyResourceMutations(ctx, - draft, - appliedResources, - []mutation{&renderPackageMutation{ - runnerOptions: runnerOptions, - runtime: th.Runtime, - }}) - } else { - renderStatus = nil - } - - return renderStatus, nil +func GetDefaultTaskHandler() TaskHandler { + return &genericTaskHandler{} } From ec14851083f1def737d9b9013c1e65b4663a6365 Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:39:33 +0000 Subject: [PATCH 12/13] Update license dates on copyright notices --- pkg/cache/cache.go | 14 ++++++++++++++ pkg/cache/fake/objectnotifier.go | 2 +- pkg/cache/memory/cache.go | 2 +- pkg/cache/memory/cache_test.go | 2 +- pkg/cache/memory/draft.go | 2 +- pkg/cache/memory/package.go | 2 +- pkg/cache/memory/packagerevision.go | 2 +- pkg/cache/memory/util.go | 2 +- pkg/engine/options.go | 2 +- pkg/engine/watchermanager.go | 2 +- pkg/git/git.go | 2 +- pkg/git/package.go | 2 +- pkg/oci/oci.go | 2 +- pkg/registry/porch/package.go | 2 +- pkg/registry/porch/packagecommon.go | 2 +- pkg/registry/porch/packagerevision.go | 2 +- pkg/registry/porch/packagerevisionresources.go | 2 +- pkg/registry/porch/reference.go | 2 +- pkg/registry/porch/watch.go | 2 +- pkg/registry/porch/watch_test.go | 2 +- pkg/repository/environment.go | 2 +- pkg/repository/fake/packagerevision.go | 2 +- pkg/repository/fake/repository.go | 2 +- pkg/repository/package.go | 2 +- pkg/repository/repository.go | 2 +- pkg/repository/testing.go | 2 +- pkg/repository/update.go | 2 +- pkg/repository/update_test.go | 2 +- pkg/task/builtin.go | 2 +- pkg/task/builtin_test.go | 2 +- pkg/task/clone.go | 2 +- pkg/task/clone_test.go | 2 +- pkg/task/edit.go | 2 +- pkg/task/edit_test.go | 2 +- pkg/task/eval.go | 2 +- pkg/task/generictaskhandler.go | 2 +- pkg/task/init.go | 2 +- pkg/task/init_test.go | 2 +- pkg/task/kio.go | 2 +- pkg/task/mergekey.go | 2 +- pkg/task/patch_test.go | 2 +- pkg/task/patchgen.go | 2 +- pkg/task/patchgen_test.go | 2 +- pkg/task/render.go | 2 +- pkg/task/render_test.go | 2 +- pkg/task/replace_test.go | 2 +- pkg/task/taskhandler.go | 2 +- pkg/util/util.go | 2 +- 48 files changed, 61 insertions(+), 47 deletions(-) diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 6b9918f5..1d411aea 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -1,3 +1,17 @@ +// Copyright 2024 The Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cache import ( diff --git a/pkg/cache/fake/objectnotifier.go b/pkg/cache/fake/objectnotifier.go index f9bde5aa..a8cdf94e 100644 --- a/pkg/cache/fake/objectnotifier.go +++ b/pkg/cache/fake/objectnotifier.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022. 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index 3cd53330..45984387 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/cache_test.go b/pkg/cache/memory/cache_test.go index de2fbc97..4f2323ae 100644 --- a/pkg/cache/memory/cache_test.go +++ b/pkg/cache/memory/cache_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/draft.go b/pkg/cache/memory/draft.go index 5e44b259..0e21b833 100644 --- a/pkg/cache/memory/draft.go +++ b/pkg/cache/memory/draft.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/package.go b/pkg/cache/memory/package.go index f8528bcc..d02093f2 100644 --- a/pkg/cache/memory/package.go +++ b/pkg/cache/memory/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index 0aed7cba..f12f8d62 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index cdb3ca1c..c69d402b 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/engine/options.go b/pkg/engine/options.go index 72997f67..21388edb 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/engine/watchermanager.go b/pkg/engine/watchermanager.go index b64c30be..768e68d3 100644 --- a/pkg/engine/watchermanager.go +++ b/pkg/engine/watchermanager.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/git/git.go b/pkg/git/git.go index bca58d2a..33b6b6a6 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/git/package.go b/pkg/git/package.go index b3c2ff26..be203b4d 100644 --- a/pkg/git/package.go +++ b/pkg/git/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index 4b683ac8..0983dbc5 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/package.go b/pkg/registry/porch/package.go index e5d8414f..e42dd1a8 100644 --- a/pkg/registry/porch/package.go +++ b/pkg/registry/porch/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagecommon.go b/pkg/registry/porch/packagecommon.go index 74ddb44b..78400c2d 100644 --- a/pkg/registry/porch/packagecommon.go +++ b/pkg/registry/porch/packagecommon.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagerevision.go b/pkg/registry/porch/packagerevision.go index 357d1ae7..64d0ff77 100644 --- a/pkg/registry/porch/packagerevision.go +++ b/pkg/registry/porch/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/packagerevisionresources.go b/pkg/registry/porch/packagerevisionresources.go index bd6fc6fc..10388484 100644 --- a/pkg/registry/porch/packagerevisionresources.go +++ b/pkg/registry/porch/packagerevisionresources.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/reference.go b/pkg/registry/porch/reference.go index 5fce1ab9..1026ca40 100644 --- a/pkg/registry/porch/reference.go +++ b/pkg/registry/porch/reference.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/watch.go b/pkg/registry/porch/watch.go index 4f5de58f..38102610 100644 --- a/pkg/registry/porch/watch.go +++ b/pkg/registry/porch/watch.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/registry/porch/watch_test.go b/pkg/registry/porch/watch_test.go index bbf91d91..fa21fa95 100644 --- a/pkg/registry/porch/watch_test.go +++ b/pkg/registry/porch/watch_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/environment.go b/pkg/repository/environment.go index 96d542dc..7fe8e7c9 100644 --- a/pkg/repository/environment.go +++ b/pkg/repository/environment.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/fake/packagerevision.go b/pkg/repository/fake/packagerevision.go index 187b6f0b..8f962065 100644 --- a/pkg/repository/fake/packagerevision.go +++ b/pkg/repository/fake/packagerevision.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/fake/repository.go b/pkg/repository/fake/repository.go index ff9abd72..9fd6c0c8 100644 --- a/pkg/repository/fake/repository.go +++ b/pkg/repository/fake/repository.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/package.go b/pkg/repository/package.go index 77836afd..e8383a6d 100644 --- a/pkg/repository/package.go +++ b/pkg/repository/package.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 91f620de..1aa2fa3f 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/testing.go b/pkg/repository/testing.go index 6a37f03f..54af916f 100644 --- a/pkg/repository/testing.go +++ b/pkg/repository/testing.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/update.go b/pkg/repository/update.go index 0843e2f6..64d3290d 100644 --- a/pkg/repository/update.go +++ b/pkg/repository/update.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/repository/update_test.go b/pkg/repository/update_test.go index e10a25d1..e18e05c9 100644 --- a/pkg/repository/update_test.go +++ b/pkg/repository/update_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/builtin.go b/pkg/task/builtin.go index d594309e..e6d56f3a 100644 --- a/pkg/task/builtin.go +++ b/pkg/task/builtin.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/builtin_test.go b/pkg/task/builtin_test.go index f6725ac1..c84eac43 100644 --- a/pkg/task/builtin_test.go +++ b/pkg/task/builtin_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/clone.go b/pkg/task/clone.go index 17928f5a..f5e2ee06 100644 --- a/pkg/task/clone.go +++ b/pkg/task/clone.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/clone_test.go b/pkg/task/clone_test.go index 8ed8b644..d9ecd57a 100644 --- a/pkg/task/clone_test.go +++ b/pkg/task/clone_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/edit.go b/pkg/task/edit.go index 59990c2b..01841465 100644 --- a/pkg/task/edit.go +++ b/pkg/task/edit.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/edit_test.go b/pkg/task/edit_test.go index 0ddcf9b3..bedeb4fd 100644 --- a/pkg/task/edit_test.go +++ b/pkg/task/edit_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/eval.go b/pkg/task/eval.go index a0e79db6..982322e3 100644 --- a/pkg/task/eval.go +++ b/pkg/task/eval.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/generictaskhandler.go b/pkg/task/generictaskhandler.go index abb2cdd4..e5ba8e51 100644 --- a/pkg/task/generictaskhandler.go +++ b/pkg/task/generictaskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2022,2024 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/init.go b/pkg/task/init.go index 5e113499..a5f7562b 100644 --- a/pkg/task/init.go +++ b/pkg/task/init.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/init_test.go b/pkg/task/init_test.go index 93c0c634..bb383f28 100644 --- a/pkg/task/init_test.go +++ b/pkg/task/init_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/kio.go b/pkg/task/kio.go index 6b253a72..390ab714 100644 --- a/pkg/task/kio.go +++ b/pkg/task/kio.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/mergekey.go b/pkg/task/mergekey.go index 93d97579..0765f842 100644 --- a/pkg/task/mergekey.go +++ b/pkg/task/mergekey.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patch_test.go b/pkg/task/patch_test.go index eb116388..32961e88 100644 --- a/pkg/task/patch_test.go +++ b/pkg/task/patch_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patchgen.go b/pkg/task/patchgen.go index 6cb5b73a..1046ba0d 100644 --- a/pkg/task/patchgen.go +++ b/pkg/task/patchgen.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/patchgen_test.go b/pkg/task/patchgen_test.go index 57aaeacc..00448358 100644 --- a/pkg/task/patchgen_test.go +++ b/pkg/task/patchgen_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/render.go b/pkg/task/render.go index dc91df2c..56a3ae71 100644 --- a/pkg/task/render.go +++ b/pkg/task/render.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/render_test.go b/pkg/task/render_test.go index c2a16268..1d1fc17a 100644 --- a/pkg/task/render_test.go +++ b/pkg/task/render_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index 571f361d..062c8805 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/task/taskhandler.go b/pkg/task/taskhandler.go index 030e5bef..e30f3336 100644 --- a/pkg/task/taskhandler.go +++ b/pkg/task/taskhandler.go @@ -1,4 +1,4 @@ -// Copyright 2024 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/util/util.go b/pkg/util/util.go index c323add7..164f9c25 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,4 +1,4 @@ -// Copyright 2023 The kpt and Nephio Authors +// Copyright 2022, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 5034cfbf55b3d4be131cd11c10301faf555ec19f Mon Sep 17 00:00:00 2001 From: liamfallon Date: Mon, 18 Nov 2024 16:41:16 +0000 Subject: [PATCH 13/13] Update license dates on copyright notices --- pkg/util/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/util.go b/pkg/util/util.go index 164f9c25..c331b586 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,4 +1,4 @@ -// Copyright 2022, 2024 The kpt and Nephio Authors +// Copyright 2023, 2024 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.