-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tarball packages can now contain local dependencies #1728
Conversation
Summary: Currently, KUDO allows local dependencies like `package: ../child` or `../child.tgz` however, only if the parent operator is *also* in a local directory. This PR adds the possibility for a packaged operator (a tarball) to contain all the dependencies it needs. While, the preferred way is to keep the individual operators in the repo, sometimes a self-contained operator package (like an uber-jar) is more desirable e.g.: - an air-gaped environment needs a functioning repo before operator installation which significantly increases the overall deployment overhead - some dependency operators might be heavily customized official ones, which makes it hard to contribute them back to the official repo To keep the packages backward-compatible, we continue to expect the parent operator at the top-level of the tarball, so that all the dependencies must share the same base path and be at least one level deeper e.g.: ``` . └── child │ └── operator.yaml └── operator.yaml ``` A dependency operator (`child` above) can have its dependencies which are always relative to the `operator.yaml` which declares them. See [this commit](c237347) for more information. Fixes #1701 Signed-off-by: Aleksey Dukhovniy <alex.dukhovniy@googlemail.com>
// PackageResolver is the source of resolver of operator packages. | ||
type PackageResolver struct { | ||
local *LocalResolver | ||
uri *URLResolver | ||
local *LocalHelper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, LocalResolver
and URLResolver
would implement the Resolver
interface:
Resolve(name string, appVersion string, operatorVersion string) (*packages.Resources, error)
However, they are merely helpers to download/parse the directory/tarballs and would ignore the appVersion
and operatorVersion
parameters. I kept their overall structure but they don't implement the interface anymore.
@@ -10,32 +12,30 @@ import ( | |||
"github.com/kudobuilder/kudo/pkg/kudoctl/util/repo" | |||
) | |||
|
|||
// Resolver will try to resolve a given package name to either local tarball, folder, remote url or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to move the Resolver
interface one level higher to avoid import cycles.
@@ -103,18 +96,10 @@ func dependencyWalk( | |||
// a relative dependency path must begin with either './' or '../' | |||
isRelativePackage := strings.HasPrefix(childPackageName, "./") || strings.HasPrefix(childPackageName, "../") | |||
if isRelativePackage { | |||
if operatorDirectory == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need in this whole dance, now that dependencies come with the scope-aware dependency resolver.
@@ -67,18 +66,6 @@ func OperatorAndOperatorVersion( | |||
} | |||
|
|||
func installDependencies(client *kudo.Client, ov *kudoapi.OperatorVersion, dependencies []deps.Dependency) error { | |||
// The KUDO controller will create Instances for the dependencies. For this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While working on this PR, I realized that updating the tasks here (in L80) had a bug, now that dependencies are relative to their parent. Consider a folder structure like:
.
└── child # first child
│ └── child # second child
│ │ └── operator.yaml
│ └── operator.yaml
└── operator.yaml
Now, the parent references the first child as ./child
and it references the second child as ./child
too. The updateKudoOperatorTasks
method below would search the dependencies by the dependency.PackageName
and update the corresponding task but with two identical PackageNames
this would lead to bugs.
I've moved this logic in the resolve.go::dependencyWalk
method. Now each task is updated when the dependency is resolved (which is arguably a much better place to do that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This setup would lead to issues anyway, right? Because the operatorName for both children is child
? It may work if they have different appVersion/operatorVersion, but if those are the same too, the generated name would clash on installation of the OV.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
./child
is the folder name, the operator name is known after it is resolved (and can be something else completely) 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. Yeah, if the operator names are different then it should work. Yeah 👍
// | ||
// so that the KUDO controller to be able to grab the right 'OperatorVersion' resources from the cluster | ||
// when the task is executed. | ||
err = updateResolvedKudoOperatorTask(childTask.Name, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this comment on why this logic has been moved here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First iteration of review. Looks great so far, my concerns are mostly around naming, because "resolve" is getting super overloaded. Will do another iteration to verify that combinations of tarball, directory and URL dependencies work as expected. I think they do, just need to double-check.
// ResourcesFromTar extracts files from the tar provides by he `inFs` and the `path` and converts | ||
// them to resources. All the extracted files are saved in the `outFs` for later use (e.g. searching | ||
// for local dependencies) | ||
func ResourcesFromTar(in afero.Fs, out afero.Fs, path string) (*packages.Resources, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a fan of having a function parameter that is an output value. Let's make this a return value. I see that this is done to inject a afero.MemMapFs
, but can't ExtractTar
just create one itself? I don't see why it's important to be able to chose the afero.Fs
implementation as a caller here, as it's only called with afero.MemMapFs
. Unless, of course, I'm missing something :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well... In this case I'm not opposed to passing in the output FS - It would allow to extract multiple tar files into the same MemFS, or into the real Filesystem, etc.
I'm not sure about the Naming though - ExtractTarIntoFs
might be more fitting. I'll have to look into it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth on passing out
as a parameter or simply returning it. In the end, I decided to pass it, with the following rationale: the caller of this method might very well decide to pass in a different kind of Fs
and frankly, this should be ok 🤷 And, as @ANeumann82 mentioned above, it makes extracting multiple tgzs into one Fs
easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is the best way to abstract away the filesystem used in these calls. However, we only use it with afero.MemMapFs
and don't need this, IMO. But I don't feel strongly about this. Though this makes the function signatures a bit harder to parse. Great, that out
is used as parameter name to clearly indicate the parameters purpose.
Thanks :) This was handed over from zen-dog, and I'm not sure it's already ready for review, he said that it needs polishing. Thanks for the comments, i'll make a pass now |
Signed-off-by: Andreas Neumann <aneumann@mesosphere.com>
Signed-off-by: Andreas Neumann <aneumann@mesosphere.com>
Signed-off-by: Andreas Neumann <aneumann@mesosphere.com>
return &InClusterResolver{c: c, ns: ns} | ||
func (m *PackageResolver) copyWithChangedWorkingDir(workingDir string) packages.Resolver { | ||
return &PackageResolver{ | ||
local: newForFilesystem(m.local.fs, workingDir), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better 👏 👏
@nfnt would you have time to look at this PR again? I believe most of your concerns were addressed. |
Signed-off-by: Andreas Neumann <aneumann@mesosphere.com> # Conflicts: # pkg/kudoctl/packages/reader/reader.go # pkg/kudoctl/packages/reader/reader_test.go # pkg/kudoctl/packages/resolver/resolver_local.go
Don't forget to put yourself as co-author @ANeumann82 😉 |
Summary:
Currently, KUDO allows local dependencies like
package: ../child
or../child.tgz
however, only if the parent operator is also in a local directory. This PR adds the possibility for a packaged operator (a tarball) to contain all the dependencies it needs. While, the preferred way is to keep the individual operators in the repo, sometimes a self-contained operator package (like an uber-jar) is more desirable e.g.:To keep the packages backward-compatible, we continue to expect the parent operator at the top-level of the tarball, so that all the dependencies must share the same base path and be at least one level deeper e.g.:
A dependency operator (
child
above) is always relative to the parentoperator.yaml
which declares it. In this case, it should be referenced like:See this commit for more information.
Fixes #1701
Signed-off-by: Aleksey Dukhovniy alex.dukhovniy@googlemail.com