Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/porch/v1alpha2/packagerevision_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ type PackageRevisionStatus struct {
// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`

// PrrSizeBytes is the total file size, in bytes, of the package revision's resources.
// +optional
PrrSizeBytes int64 `json:"prrSizeBytes,omitempty"`
}

// PackageSource specifies how a package was created.
Expand Down
5 changes: 5 additions & 0 deletions api/porch/v1alpha2/porch.kpt.dev_packagerevisions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@ spec:
- type
type: object
type: array
prrSizeBytes:
description: PrrSizeBytes is the total file size, in bytes, of the
package revision's resources.
format: int64
type: integer
publishedAt:
description: PublishedAt is the time when the packagerevision were
approved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"testing"
"time"

kptfilev1 "github.com/kptdev/kpt/pkg/api/kptfile/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
kptfilev1 "github.com/kptdev/kpt/pkg/api/kptfile/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -31,6 +31,7 @@ func setupMockContentDefaults(m *mockrepository.MockPackageContent) {
m.EXPECT().GetCommitInfo().Return(time.Time{}, "").Maybe()
m.EXPECT().GetLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil).Maybe()
m.EXPECT().GetUpstreamLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil).Maybe()
m.EXPECT().GetResourceContents(mock.Anything).Return(map[string]string{"Kptfile": "test"}, nil).Maybe()
}

func newTestReconciler(mockClient *mockclient.MockClient, cache *mockrepository.MockContentCache) *PackageRevisionReconciler {
Expand Down Expand Up @@ -385,7 +386,7 @@ func TestReconcileDeletionProposedNoFinalizer(t *testing.T) {
*obj.(*porchv1alpha2.PackageRevision) = *pr
}).Return(nil)
// No Patch expected — finalizer already absent.

// updateLatestRevisionLabels is called after finalizer removal
mockClient.EXPECT().List(mock.Anything, mock.AnythingOfType("*v1alpha2.PackageRevisionList"), mock.Anything, mock.Anything).Return(nil)

Expand Down Expand Up @@ -715,7 +716,7 @@ func TestReconcileOwnerRefRepoLookupFails(t *testing.T) {

pr := &porchv1alpha2.PackageRevision{
ObjectMeta: metav1.ObjectMeta{Name: "test-pr", Namespace: "default"},
Spec: porchv1alpha2.PackageRevisionSpec{RepositoryName: "missing-repo"},
Spec: porchv1alpha2.PackageRevisionSpec{RepositoryName: "missing-repo"},
}

mockClient := mockclient.NewMockClient(t)
Expand Down Expand Up @@ -1206,7 +1207,6 @@ func TestReconcileNoSource(t *testing.T) {
assert.Equal(t, ctrl.Result{}, result)
}


// mockRenderer is a test double for the renderer interface.
type mockRenderer struct {
resources map[string]string
Expand Down Expand Up @@ -1294,7 +1294,6 @@ func TestReconcileRenderAlreadyRendered(t *testing.T) {
assert.Nil(t, result)
}


func TestReconcileRenderSourceTrigger(t *testing.T) {
ctx := t.Context()
rendered := map[string]string{"Kptfile": "rendered"}
Expand Down Expand Up @@ -1553,7 +1552,6 @@ func TestReconcileSourceCloseDraftFails(t *testing.T) {
assert.Equal(t, ctrl.Result{}, result)
}


func TestReconcileRenderErrorSetsStatus(t *testing.T) {
// Reconcile should handle reconcileRender returning an error
// by logging and returning (no crash, no requeue).
Expand Down Expand Up @@ -1665,7 +1663,6 @@ func TestWriteRenderedResourcesCloseDraftFails(t *testing.T) {
assert.Contains(t, err.Error(), "close draft after render")
}


func TestReconcileRenderPipelineFailureNoPush(t *testing.T) {
ctx := t.Context()

Expand Down Expand Up @@ -1760,7 +1757,6 @@ func TestReconcileRenderPipelineFailureWithPushWriteFails(t *testing.T) {
assert.Nil(t, result)
}


func TestRenderWithConcurrencyLimitRequeues(t *testing.T) {
limiter := make(chan struct{}, 1)
limiter <- struct{}{} // fill the limiter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func (r *PackageRevisionReconciler) updateStatus(ctx context.Context, pr *porchv
if _, upstreamLock, err := content.GetUpstreamLock(ctx); err == nil {
status.UpstreamLock = porchv1alpha2.KptLocatorToLocator(upstreamLock)
}
if resources, err := content.GetResourceContents(ctx); err == nil {
status.PrrSizeBytes = repository.CalculateResourcesSize(resources)
}
Comment thread
efiacor marked this conversation as resolved.
}

applyObj := &porchv1alpha2.PackageRevision{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func TestUpdateStatusWithPublishedContent(t *testing.T) {
content.EXPECT().GetCommitInfo().Return(commitTime, "user@example.com")
content.EXPECT().GetLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil)
content.EXPECT().GetUpstreamLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil)
content.EXPECT().GetResourceContents(mock.Anything).Return(map[string]string{"Kptfile": "abc", "cm.yaml": "defgh"}, nil)

r := &PackageRevisionReconciler{Client: mockClient}
pr := basePR()
Expand All @@ -99,6 +100,7 @@ func TestUpdateStatusWithPublishedContent(t *testing.T) {
assert.Equal(t, 5, captured.Revision)
assert.Equal(t, "user@example.com", captured.PublishedBy)
assert.NotNil(t, captured.PublishedAt)
assert.Equal(t, int64(8), captured.PrrSizeBytes)
}

func TestUpdateStatusWithDraftContent(t *testing.T) {
Expand All @@ -109,6 +111,7 @@ func TestUpdateStatusWithDraftContent(t *testing.T) {
content.EXPECT().Lifecycle(mock.Anything).Return("Draft")
content.EXPECT().GetLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil)
content.EXPECT().GetUpstreamLock(mock.Anything).Return(kptfilev1.Upstream{}, kptfilev1.Locator{}, nil)
content.EXPECT().GetResourceContents(mock.Anything).Return(map[string]string{"Kptfile": "draft-content"}, nil)

r := &PackageRevisionReconciler{Client: mockClient}
pr := basePR()
Expand All @@ -118,6 +121,7 @@ func TestUpdateStatusWithDraftContent(t *testing.T) {
assert.Equal(t, 0, captured.Revision)
assert.Empty(t, captured.PublishedBy)
assert.Nil(t, captured.PublishedAt)
assert.Equal(t, int64(13), captured.PrrSizeBytes)
}

func TestUpdateRenderStatusInProgress(t *testing.T) {
Expand Down Expand Up @@ -180,7 +184,6 @@ func TestSetRenderFailed(t *testing.T) {
assert.Equal(t, porchv1alpha2.ReasonRenderFailed, renderPatch.Conditions[0].Reason)
}


func TestUpdateKptfileFields(t *testing.T) {
mockClient := mockclient.NewMockClient(t)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func (r *RepositoryReconciler) applySeedFields(ctx context.Context, repo *config
func packageRevisionUpToDate(existing, desired *porchv1alpha2.PackageRevision) bool {
return equality.Semantic.DeepEqual(existing.Labels, desired.Labels) &&
existing.Status.Deployment == desired.Status.Deployment &&
existing.Status.PrrSizeBytes == desired.Status.PrrSizeBytes &&
equality.Semantic.DeepEqual(existing.Status.UpstreamLock, desired.Status.UpstreamLock) &&
equality.Semantic.DeepEqual(existing.Status.SelfLock, desired.Status.SelfLock)
}
Expand All @@ -219,6 +220,11 @@ func buildPackageRevision(ctx context.Context, repo *configapi.Repository, pkgRe
// PackageConditions omitted — PR controller owns after first render.
}

// Calculate resource size for status.prrSizeBytes.
if prr, err := pkgRev.GetResources(ctx); err == nil && prr != nil && prr.Spec.Resources != nil {
status.PrrSizeBytes = repository.CalculateResourcesSize(prr.Spec.Resources)
}
Comment thread
efiacor marked this conversation as resolved.

crd := &porchv1alpha2.PackageRevision{
TypeMeta: metav1.TypeMeta{
Kind: "PackageRevision",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type fakePackageRevision struct {
commitTime time.Time
commitAuthor string
isLatest bool
resources map[string]string
}

func (f *fakePackageRevision) KubeObjectNamespace() string { return f.key.RKey().Namespace }
Expand All @@ -110,6 +111,13 @@ func (f *fakePackageRevision) GetPackageRevision(_ context.Context) (*porchv1alp
return nil, nil
}
func (f *fakePackageRevision) GetResources(_ context.Context) (*porchv1alpha1.PackageRevisionResources, error) {
if f.resources != nil {
return &porchv1alpha1.PackageRevisionResources{
Spec: porchv1alpha1.PackageRevisionResourcesSpec{
Resources: f.resources,
},
}, nil
}
return nil, nil
}
func (f *fakePackageRevision) GetUpstreamLock(_ context.Context) (kptfilev1.Upstream, kptfilev1.Locator, error) {
Expand Down Expand Up @@ -219,6 +227,27 @@ func TestBuildPackageRevision(t *testing.T) {
assert.Nil(t, crd.Status.UpstreamLock)
assert.Nil(t, crd.Status.SelfLock)
})

t.Run("PrrSizeBytes calculated from resources", func(t *testing.T) {
pkgRev := newFakePkgRev("sized-pkg", "ws1", porchv1alpha2.PackageRevisionLifecyclePublished)
pkgRev.resources = map[string]string{
"Kptfile": "abc", // 3 bytes
"cm.yaml": "defgh", // 5 bytes
"ns.yaml": "ij", // 2 bytes
}

crd, err := buildPackageRevision(ctx, repo, pkgRev)
assert.NoError(t, err)
assert.Equal(t, int64(10), crd.Status.PrrSizeBytes)
})

t.Run("PrrSizeBytes zero when no resources", func(t *testing.T) {
pkgRev := newFakePkgRev("empty-pkg", "ws1", porchv1alpha2.PackageRevisionLifecycleDraft)

crd, err := buildPackageRevision(ctx, repo, pkgRev)
assert.NoError(t, err)
assert.Equal(t, int64(0), crd.Status.PrrSizeBytes)
})
}

// --- Tests: packageRevisionUpToDate ---
Expand Down Expand Up @@ -253,6 +282,9 @@ func TestPackageRevisionUpToDate(t *testing.T) {
{name: "annotations differ - still up to date", modify: func(pr *porchv1alpha2.PackageRevision) {
pr.Annotations = map[string]string{"foo": "bar"}
}, expected: true},
{name: "PrrSizeBytes changed", modify: func(pr *porchv1alpha2.PackageRevision) {
pr.Status.PrrSizeBytes = 12345
}, expected: false},
}

for _, tt := range tests {
Expand Down
10 changes: 10 additions & 0 deletions pkg/repository/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@ func PackageRevisionIsPlaceholder(ctx context.Context, namespace string, referen
return false, nil
}

// CalculateResourcesSize returns the total byte size of a package's resource
// file contents. This is used to populate status.prrSizeBytes on the CRD.
func CalculateResourcesSize(resources map[string]string) int64 {
var total int64
for _, v := range resources {
total += int64(len(v))
}
return total
}

func WriteResourcesToFS(fs filesys.FileSystem, rootDir string, resources map[string]string) (string, error) {
if rootDir != "" {
if err := fs.MkdirAll(rootDir); err != nil {
Expand Down
45 changes: 45 additions & 0 deletions pkg/repository/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,51 @@ func TestPathsOverlap(t *testing.T) {
assert.False(t, PathsOverlap("pkg", "pkg-other"))
}

func TestCalculateResourcesSize(t *testing.T) {
tests := []struct {
name string
resources map[string]string
want int64
}{
{
name: "nil map",
resources: nil,
want: 0,
},
{
name: "empty map",
resources: map[string]string{},
want: 0,
},
{
name: "single file",
resources: map[string]string{"Kptfile": "hello"},
want: 5,
},
{
name: "multiple files",
resources: map[string]string{
"Kptfile": "abc",
"cm.yaml": "defgh",
"nested.txt": "ij",
},
want: 10,
},
{
name: "multi-byte UTF-8 characters",
resources: map[string]string{"file.yaml": "héllo"}, // é is 2 bytes in UTF-8
want: 6,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CalculateResourcesSize(tt.resources)
assert.Equal(t, tt.want, got)
})
}
}

func TestValidatePackagePathOverlap(t *testing.T) {
newPr := &porchapi.PackageRevision{
Spec: porchapi.PackageRevisionSpec{
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/crd/clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ var _ = Describe("Clone", Ordered, Label("lifecycle"), func() {
Expect(pr.Status.SelfLock).NotTo(BeNil())
Expect(pr.Status.SelfLock.Git).NotTo(BeNil())
Expect(pr.Status.SelfLock.Git.Commit).NotTo(BeEmpty())

By("verifying PrrSizeBytes is populated")
Expect(pr.Status.PrrSizeBytes).To(BeNumerically(">", int64(0)))
})

It("should clone into a deployment repository", func() {
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/crd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ var _ = Describe("Init", Ordered, Label("lifecycle"), func() {
By("verifying Kptfile exists in package content")
resources := getPRRResources(env.Ctx, env.Namespace, pr.Name)
Expect(resources).To(HaveKey("Kptfile"))

By("verifying PrrSizeBytes is populated")
Expect(pr.Status.PrrSizeBytes).To(BeNumerically(">", int64(0)))
})

It("should init a package with full metadata", func() {
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/crd/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ var _ = Describe("Push", Ordered, Label("content"), func() {
waitForReady(env.Ctx, pr)
waitForPRRVisible(env.Ctx, env.Namespace, pr.Name)

By("recording initial PrrSizeBytes")
initialSize := pr.Status.PrrSizeBytes
Expect(initialSize).To(BeNumerically(">", int64(0)))

By("pushing a new ConfigMap via PRR")
updatePRRResources(env.Ctx, env.Namespace, pr.Name, map[string]string{
"configmap.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: push-test-cm\ndata:\n key: value\n",
Expand All @@ -52,6 +56,12 @@ var _ = Describe("Push", Ordered, Label("content"), func() {
g.Expect(resources["configmap.yaml"]).To(ContainSubstring("push-test-cm"))
g.Expect(resources).To(HaveKey("Kptfile"))
}).WithTimeout(defaultTimeout).WithPolling(defaultInterval).Should(Succeed())

By("verifying PrrSizeBytes increased after push")
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(env.Ctx, client.ObjectKeyFromObject(pr), pr)).To(Succeed())
g.Expect(pr.Status.PrrSizeBytes).To(BeNumerically(">", initialSize))
}).WithTimeout(defaultTimeout).WithPolling(defaultInterval).Should(Succeed())
})

It("should handle empty PRR update without error", func() {
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/crd/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ var _ = Describe("Repository", Ordered, Label("infra"), func() {
Expect(pr.Spec.PackageName).To(Equal("basens"))
Expect(pr.Spec.RepositoryName).To(Equal(testBlueprintsRepo))
Expect(pr.Spec.Lifecycle).To(Equal(porchv1alpha2.PackageRevisionLifecyclePublished))

By("verifying PrrSizeBytes is populated for discovered package")
Expect(pr.Status.PrrSizeBytes).To(BeNumerically(">", int64(0)))
})

It("should seed lifecycle and revision for discovered published packages", func() {
Expand Down
Loading