Skip to content

Commit 58f1f32

Browse files
authored
Respect Registration Directory (#3017)
If a sub-directory is specified at repository registration, the repository will only include package revisions in the specified directory. Likewise, it will only create packages in the specified subdirectory. In order to maintain stable resource names within the repo (regardless of the registration parameters), the package names are unaffected by the directory projection.
1 parent 07f2645 commit 58f1f32

File tree

4 files changed

+208
-2
lines changed

4 files changed

+208
-2
lines changed

porch/repository/pkg/git/dir.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package git
16+
17+
import "strings"
18+
19+
// Determines whether a package specified by a path is in a directory given.
20+
func packageInDirectory(pkg, dir string) bool {
21+
if dir == "" {
22+
return true
23+
}
24+
if strings.HasPrefix(pkg, dir) {
25+
if len(pkg) == len(dir) {
26+
return true
27+
}
28+
if pkg[len(dir)] == '/' {
29+
return true
30+
}
31+
}
32+
return false
33+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package git
16+
17+
import "testing"
18+
19+
func TestPackageInDirectory(t *testing.T) {
20+
for _, tc := range []struct {
21+
pkg, dir string
22+
want bool
23+
}{
24+
{
25+
pkg: "root/nested",
26+
dir: "",
27+
want: true,
28+
}, {
29+
pkg: "catalog/package",
30+
dir: "cat",
31+
want: false,
32+
},
33+
{
34+
pkg: "catalog/package",
35+
dir: "catalog",
36+
want: true,
37+
},
38+
{
39+
pkg: "catalog/package/nested",
40+
dir: "catalog/packages",
41+
want: false,
42+
},
43+
{
44+
pkg: "catalog/package/nested",
45+
dir: "catalog/package",
46+
want: true,
47+
},
48+
{
49+
pkg: "catalog/package/nested",
50+
dir: "catalog/package/nest",
51+
want: false,
52+
},
53+
{
54+
pkg: "catalog/package/nested",
55+
dir: "catalog/package/nested",
56+
want: true,
57+
},
58+
{
59+
pkg: "catalog/package/nested",
60+
dir: "catalog/package/nested/even-more",
61+
want: false,
62+
},
63+
} {
64+
if got, want := packageInDirectory(tc.pkg, tc.dir), tc.want; got != want {
65+
t.Errorf("packageInDirectory(%q, %q): got %t, want %t", tc.pkg, tc.dir, got, want)
66+
}
67+
}
68+
}

porch/repository/pkg/git/git.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func OpenRepository(ctx context.Context, name, namespace string, spec *configapi
100100
namespace: namespace,
101101
repo: repo,
102102
branch: branch,
103+
directory: strings.Trim(spec.Directory, "/"),
103104
secret: spec.SecretRef.Name,
104105
credentialResolver: opts.CredentialResolver,
105106
userInfoProvider: opts.UserInfoProvider,
@@ -117,6 +118,7 @@ type gitRepository struct {
117118
namespace string // Repository resource namespace
118119
secret string // Name of the k8s Secret resource containing credentials
119120
branch BranchName // The main branch from repository registration (defaults to 'main' if unspecified)
121+
directory string // Directory within the repository where to look for packages.
120122
repo *git.Repository
121123
cachedCredentials transport.AuthMethod
122124
credentialResolver repository.CredentialResolver
@@ -184,6 +186,10 @@ func (r *gitRepository) ListPackageRevisions(ctx context.Context) ([]repository.
184186
}
185187

186188
func (r *gitRepository) CreatePackageRevision(ctx context.Context, obj *v1alpha1.PackageRevision) (repository.PackageDraft, error) {
189+
if !packageInDirectory(obj.Spec.PackageName, r.directory) {
190+
return nil, fmt.Errorf("cannot create %s outside of repository directory %q", obj.Spec.PackageName, r.directory)
191+
}
192+
187193
var base plumbing.Hash
188194
refName := r.branch.RefInLocal()
189195
switch main, err := r.repo.Reference(refName, true); {
@@ -345,6 +351,10 @@ func (r *gitRepository) GetPackage(version, path string) (repository.PackageRevi
345351
}
346352

347353
func (r *gitRepository) loadPackageRevision(version, path string, hash plumbing.Hash) (repository.PackageRevision, kptfilev1.GitLock, error) {
354+
if !packageInDirectory(path, r.directory) {
355+
return nil, kptfilev1.GitLock{}, fmt.Errorf("cannot find package %s@%s; package is not under the Repository.spec.directory", path, version)
356+
}
357+
348358
git := r.repo
349359

350360
origin, err := git.Remote("origin")
@@ -402,6 +412,16 @@ func (r *gitRepository) discoverFinalizedPackages(ref *plumbing.Reference) ([]re
402412
return nil, err
403413
}
404414

415+
var result []repository.PackageRevision
416+
417+
// Recurse into the specified directory
418+
if r.directory != "" {
419+
tree, err = tree.Tree(r.directory)
420+
if err == object.ErrDirectoryNotFound {
421+
return result, nil
422+
}
423+
}
424+
405425
var revision string
406426
if rev, ok := getBranchNameInLocalRepo(ref.Name()); ok {
407427
revision = rev
@@ -412,8 +432,7 @@ func (r *gitRepository) discoverFinalizedPackages(ref *plumbing.Reference) ([]re
412432
return nil, fmt.Errorf("cannot determine revision from ref: %q", rev)
413433
}
414434

415-
var result []repository.PackageRevision
416-
if err := discoverPackagesInTree(git, tree, "", func(dir string, tree, kptfile plumbing.Hash) error {
435+
if err := discoverPackagesInTree(git, tree, r.directory, func(dir string, tree, kptfile plumbing.Hash) error {
417436
result = append(result, &gitPackageRevision{
418437
parent: r,
419438
path: dir,
@@ -467,6 +486,11 @@ func (r *gitRepository) loadDraft(ref *plumbing.Reference) (*gitPackageRevision,
467486
return nil, err
468487
}
469488

489+
// Only load drafts in the directory specified at repository registration.
490+
if !packageInDirectory(name, r.directory) {
491+
return nil, nil
492+
}
493+
470494
commit, err := r.repo.CommitObject(ref.Hash())
471495
if err != nil {
472496
return nil, fmt.Errorf("cannot resolve draft branch to commit (corrupted repository?): %w", err)
@@ -546,6 +570,10 @@ func (r *gitRepository) loadTaggedPackages(tag *plumbing.Reference) ([]repositor
546570
// tag=<package path>/version
547571
path, revision := name[:slash], name[slash+1:]
548572

573+
if !packageInDirectory(path, r.directory) {
574+
return nil, nil
575+
}
576+
549577
commit, err := r.repo.CommitObject(tag.Hash())
550578
if err != nil {
551579
return nil, fmt.Errorf("cannot resolve tag %q to commit (corrupted repository?): %w", name, err)

porch/repository/pkg/git/git_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,3 +932,80 @@ func TestNested(t *testing.T) {
932932
t.Errorf("Discovered packages differ: (-want,+got): %s", cmp.Diff(want, got))
933933
}
934934
}
935+
936+
func createPackageRevisionMap(revisions []repository.PackageRevision) map[string]bool {
937+
result := map[string]bool{}
938+
for _, pr := range revisions {
939+
key := pr.Key()
940+
result[fmt.Sprintf("%s/%s", key.Package, key.Revision)] = true
941+
}
942+
return result
943+
}
944+
945+
func sliceToSet(s []string) map[string]bool {
946+
result := map[string]bool{}
947+
for _, v := range s {
948+
result[v] = true
949+
}
950+
return result
951+
}
952+
953+
func TestNestedDirectories(t *testing.T) {
954+
ctx := context.Background()
955+
956+
for _, tc := range []struct {
957+
directory string
958+
packages []string
959+
}{
960+
{
961+
directory: "sample",
962+
packages: []string{"sample/v1", "sample/v2", "sample/main"},
963+
},
964+
{
965+
directory: "nonexistent",
966+
packages: []string{},
967+
},
968+
{
969+
directory: "catalog/gcp",
970+
packages: []string{
971+
"catalog/gcp/cloud-sql/v1",
972+
"catalog/gcp/spanner/v1",
973+
"catalog/gcp/bucket/v2",
974+
"catalog/gcp/bucket/v1",
975+
"catalog/gcp/bucket/main",
976+
},
977+
},
978+
} {
979+
t.Run(tc.directory, func(t *testing.T) {
980+
tempdir := t.TempDir()
981+
tarfile := filepath.Join("testdata", "nested-repository.tar")
982+
_, address := ServeGitRepository(t, tarfile, tempdir)
983+
984+
const (
985+
repositoryName = "directory"
986+
namespace = "default"
987+
)
988+
989+
git, err := OpenRepository(ctx, repositoryName, namespace, &configapi.GitRepository{
990+
Repo: address,
991+
Branch: "main",
992+
Directory: tc.directory,
993+
}, tempdir, GitRepositoryOptions{})
994+
if err != nil {
995+
t.Fatalf("Failed to open Git repository loaded from %q with directory %q: %v", tarfile, tc.directory, err)
996+
}
997+
998+
revisions, err := git.ListPackageRevisions(ctx)
999+
if err != nil {
1000+
t.Fatalf("Failed to list packages from %q: %v", tarfile, err)
1001+
}
1002+
1003+
got := createPackageRevisionMap(revisions)
1004+
want := sliceToSet(tc.packages)
1005+
1006+
if !cmp.Equal(want, got) {
1007+
t.Errorf("Packages rooted in %q; Unexpected result (-want,+got): %s", tc.directory, cmp.Diff(want, got))
1008+
}
1009+
})
1010+
}
1011+
}

0 commit comments

Comments
 (0)