Skip to content

Commit 0d18c59

Browse files
feat(oci): initial oci-layout pull implementation
1 parent 0a2bb48 commit 0d18c59

File tree

4 files changed

+392
-7
lines changed

4 files changed

+392
-7
lines changed

api/internal/loader/fileloader.go

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"sigs.k8s.io/kustomize/api/ifc"
1616
"sigs.k8s.io/kustomize/api/internal/git"
17+
"sigs.k8s.io/kustomize/api/internal/oci"
1718
"sigs.k8s.io/kustomize/kyaml/errors"
1819
"sigs.k8s.io/kustomize/kyaml/filesys"
1920
)
@@ -93,6 +94,10 @@ type FileLoader struct {
9394
// obtained from the given repository.
9495
repoSpec *git.RepoSpec
9596

97+
// If this is non-nil, the files were
98+
// obtained from the given oci repository.
99+
ociSpec *oci.RepoSpec
100+
96101
// File system utilities.
97102
fSys filesys.FileSystem
98103

@@ -102,6 +107,9 @@ type FileLoader struct {
102107
// Used to clone repositories.
103108
cloner git.Cloner
104109

110+
// Used to pull registry images
111+
puller oci.Puller
112+
105113
// Used to clean up, as needed.
106114
cleaner func() error
107115
}
@@ -112,6 +120,9 @@ func (fl *FileLoader) Repo() string {
112120
if fl.repoSpec != nil {
113121
return fl.repoSpec.Dir.String()
114122
}
123+
if fl.ociSpec != nil {
124+
return fl.ociSpec.Dir.String()
125+
}
115126
return ""
116127
}
117128

@@ -129,20 +140,21 @@ func NewLoaderOrDie(
129140
log.Fatalf("unable to make loader at '%s'; %v", path, err)
130141
}
131142
return newLoaderAtConfirmedDir(
132-
lr, root, fSys, nil, git.ClonerUsingGitExec)
143+
lr, root, fSys, nil, git.ClonerUsingGitExec, nil)
133144
}
134145

135146
// newLoaderAtConfirmedDir returns a new FileLoader with given root.
136147
func newLoaderAtConfirmedDir(
137148
lr LoadRestrictorFunc,
138149
root filesys.ConfirmedDir, fSys filesys.FileSystem,
139-
referrer *FileLoader, cloner git.Cloner) *FileLoader {
150+
referrer *FileLoader, cloner git.Cloner, puller oci.Puller) *FileLoader {
140151
return &FileLoader{
141152
loadRestrictor: lr,
142153
root: root,
143154
referrer: referrer,
144155
fSys: fSys,
145156
cloner: cloner,
157+
puller: puller,
146158
cleaner: func() error { return nil },
147159
}
148160
}
@@ -184,6 +196,16 @@ func (fl *FileLoader) New(path string) (ifc.Loader, error) {
184196
repoSpec, fl.fSys, fl, fl.cloner)
185197
}
186198

199+
ociSpec, err := oci.NewRepoSpecFromURL(path)
200+
if err == nil {
201+
// Treat this as git repo clone request.
202+
if err = fl.errIfOciRepoCycle(ociSpec); err != nil {
203+
return nil, err
204+
}
205+
return newLoaderAtOciPull(
206+
ociSpec, fl.fSys, fl, fl.puller)
207+
}
208+
187209
if filepath.IsAbs(path) {
188210
return nil, fmt.Errorf("new root '%s' cannot be absolute", path)
189211
}
@@ -201,11 +223,14 @@ func (fl *FileLoader) New(path string) (ifc.Loader, error) {
201223
if err = fl.errIfGitContainmentViolation(root); err != nil {
202224
return nil, err
203225
}
226+
if err = fl.errIfOciContainmentViolation(root); err != nil {
227+
return nil, err
228+
}
204229
if err = fl.errIfArgEqualOrHigher(root); err != nil {
205230
return nil, err
206231
}
207232
return newLoaderAtConfirmedDir(
208-
fl.loadRestrictor, root, fl.fSys, fl, fl.cloner), nil
233+
fl.loadRestrictor, root, fl.fSys, fl, fl.cloner, fl.puller), nil
209234
}
210235

211236
// newLoaderAtGitClone returns a new Loader pinned to a temporary
@@ -253,6 +278,51 @@ func newLoaderAtGitClone(
253278
}, nil
254279
}
255280

281+
// newLoaderAtOciPull returns a new Loader pinned to a temporary
282+
// directory holding a pulled oci artifact.
283+
func newLoaderAtOciPull(
284+
repoSpec *oci.RepoSpec, fSys filesys.FileSystem,
285+
referrer *FileLoader, puller oci.Puller) (ifc.Loader, error) {
286+
cleaner := repoSpec.Cleaner(fSys)
287+
err := puller(repoSpec)
288+
if err != nil {
289+
cleaner()
290+
return nil, err
291+
}
292+
root, f, err := fSys.CleanedAbs(repoSpec.AbsPath())
293+
if err != nil {
294+
cleaner()
295+
return nil, err
296+
}
297+
// We don't know that the path requested in repoSpec
298+
// is a directory until we actually clone it and look
299+
// inside. That just happened, hence the error check
300+
// is here.
301+
if f != "" {
302+
cleaner()
303+
return nil, fmt.Errorf(
304+
"'%s' refers to file '%s'; expecting directory",
305+
repoSpec.AbsPath(), f)
306+
}
307+
// Path in repo can contain symlinks that exit repo. We can only
308+
// check for this after cloning repo.
309+
if !root.HasPrefix(repoSpec.PullDir()) {
310+
_ = cleaner()
311+
return nil, fmt.Errorf("%q refers to directory outside of repo %q", repoSpec.AbsPath(),
312+
repoSpec.PullDir())
313+
}
314+
return &FileLoader{
315+
// Pulls never allowed to escape root.
316+
loadRestrictor: RestrictionRootOnly,
317+
root: root,
318+
referrer: referrer,
319+
ociSpec: repoSpec,
320+
fSys: fSys,
321+
puller: puller,
322+
cleaner: cleaner,
323+
}, nil
324+
}
325+
256326
func (fl *FileLoader) errIfGitContainmentViolation(
257327
base filesys.ConfirmedDir) error {
258328
containingRepo := fl.containingRepo()
@@ -269,6 +339,22 @@ func (fl *FileLoader) errIfGitContainmentViolation(
269339
return nil
270340
}
271341

342+
func (fl *FileLoader) errIfOciContainmentViolation(
343+
base filesys.ConfirmedDir) error {
344+
containingRepo := fl.containingOciRepo()
345+
if containingRepo == nil {
346+
return nil
347+
}
348+
if !base.HasPrefix(containingRepo.PullDir()) {
349+
return fmt.Errorf(
350+
"security; bases in kustomizations found in "+
351+
"pulled oci repos must be within the repo, "+
352+
"but base '%s' is outside '%s'",
353+
base, containingRepo.PullDir())
354+
}
355+
return nil
356+
}
357+
272358
// Looks back through referrers for a git repo, returning nil
273359
// if none found.
274360
func (fl *FileLoader) containingRepo() *git.RepoSpec {
@@ -281,6 +367,18 @@ func (fl *FileLoader) containingRepo() *git.RepoSpec {
281367
return fl.referrer.containingRepo()
282368
}
283369

370+
// Looks back through referrers for an oci repo, returning nil
371+
// if none found.
372+
func (fl *FileLoader) containingOciRepo() *oci.RepoSpec {
373+
if fl.ociSpec != nil {
374+
return fl.ociSpec
375+
}
376+
if fl.referrer == nil {
377+
return nil
378+
}
379+
return fl.referrer.containingOciRepo()
380+
}
381+
284382
// errIfArgEqualOrHigher tests whether the argument,
285383
// is equal to or above the root of any ancestor.
286384
func (fl *FileLoader) errIfArgEqualOrHigher(
@@ -314,6 +412,24 @@ func (fl *FileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
314412
return fl.referrer.errIfRepoCycle(newRepoSpec)
315413
}
316414

415+
// TODO(monopole): Distinguish tags/digests?
416+
// I.e. Allow a distinction between oci artifacts with
417+
// path foo and tag bar and a URI with the same
418+
// path but a different tag/digest?
419+
func (fl *FileLoader) errIfOciRepoCycle(newRepoSpec *oci.RepoSpec) error {
420+
// TODO(monopole): Use parsed data instead of Raw().
421+
if fl.ociSpec != nil &&
422+
strings.HasPrefix(fl.ociSpec.Raw(), newRepoSpec.Raw()) {
423+
return fmt.Errorf(
424+
"cycle detected: URI '%s' referenced by previous URI '%s'",
425+
newRepoSpec.Raw(), fl.ociSpec.Raw())
426+
}
427+
if fl.referrer == nil {
428+
return nil
429+
}
430+
return fl.referrer.errIfOciRepoCycle(newRepoSpec)
431+
}
432+
317433
// Load returns the content of file at the given path,
318434
// else an error. Relative paths are taken relative
319435
// to the root.

0 commit comments

Comments
 (0)