-
Notifications
You must be signed in to change notification settings - Fork 0
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
composition: Enable layering of pallets #253
Comments
Test cases which must be handled include:
In each case, we need to handle overrides of particular files which might be in any pallet. Thinking carefully about the tree structure of this file import problem may help make the design of the file import mechanism more rigorous with respect to combining transitive file imports from disparate pallets. |
Here's a formal mathematical description of the file import-based pallet layering mechanism I am considering; the process of constructing this description helped me to design this mechanism, but it might not be very useful beyond that purpose (because we can't run it through a logic checker, and documentation should be done in English):
The overall result of this layering system should be a declarative equivalent of the imperative We can decompose IT(P)(p) and IS(P)(p) as follows:
With this design, Forklift can arbitrarily reorder the sequence of import declaration files d ∈ ∪{ID(P)(p) | p ∈ D(P)} for evaluating the import declaration files, without affecting the value of ( N'(P), F'(P) ) - just like how Forklift can arbitrarily reorder the loading of packages required by a pallet and arbitrarily reorder the loading of package deployments in a pallet without affecting the actual result of applying the pallet. And sensitivity to ordering continues to be allowed within - limited to be within - the scope of a file. For example, the sequence in which a package deployment's feature flags are applied (which determines the order in which certain Docker Compose files override other Docker Compose files, and which may determine the order in which certain file exports override other file exports) is encapsulated within the package deployment file, and the sequence of operations by which ITD(P)(p)(d) and ISD(P)(p)(d) are constructed for any given d ∈ ID(P)(p) is encapsulated within file d. |
Now that I've implemented file import groups declared as files within the |
Currently, the only way to make a variant of a pallet is to fork it into a new repo and then synchronize changes between the upstream repo and the downstream fork; any changes from other upstream pallets must be manually copied from those upstreams, with no support in git for synchronizing future changes from those upstreams. Making variants of pallets would be easier if we could take a "layering" approach (like how container images can be composed by copying files from multiple other container images). Prototypical motivating use-cases include:
none
hardware type, while github.com/PlanktoScope/device-pallet-segmenter-only could be the pallet used by the PlanktoScope SD card images in thesegmenter-only
hardware type.Just like how pallets have a
requirements/repositories
subdirectory which is manipulated with the[dev] plt add-repo
and[dev] plt rm-repo
subcommands, we can add arequirements/pallets
subdirectory which also usesforklift-version-lock.yml
files, and we can add[dev] plt add-plt
and[dev] plt rm-plt
subcommands. Note that we'd probably want to provide a way to easily (check for and) switch to newly-released versions of required pallets, as a parallel to #246. This could be implemented by manually adding/requirements/pallets/{pallet path}/forklift-updates.yml
files, for example.Then we need to figure out how to organize the inclusion of files (e.g. files/directories under
requirements
and/or files/directories underdeployments
) from required pallets, e.g. with one or more of the following options:Mirroring the filesystem structure, we could allow listing files in
forklift-includes.yml
files which can exist anywhere. Wherever such a file exists, it can specify one or more file imports; each file import consists of:github.com/PlanktoScope/pallet-standard/deployments/infra
)forklift-includes.yml
file, if the target subdirectory path is different from the subdirectory path of the file to import relative to its parent pallet (e.g. the/deployments/forklift-includes.yml
could specify the target asbase-infra
; or the/forklift-includes.yml
file could specify the target asdeployments/base-infra
)This option could be very familiar with the syntax for specifying file exports from Forklift packages, and placing
forklift-includes.yml
files next to the places where files will be imported provides some nice locality between declarations and their effects, and it's also familiar with the idea of importing certain Go packages in certain other Go packages. On the other hand, allowingforklift-includes.yml
files to be anywhere other than the root of the pallet would expose some additional complexity for people who need to understand what files get included where. We could start with only allowingforklift-includes.yml
at the root of the pallet and then later experiment with the ramifications (for transitive layering and for composition of files from multiple upstream pallets) of allowingforklift-includes.yml
files in subdirectories of the pallet.It's not clear to me how we could inherit everything in a required pallet's
forklift-includes.yml
file but narrow the files imported from it, or combine it with additional files in the same directory provided by some other required pallet. So I'm not sure the advantages of this approach outweigh its limitations and its complexity.Adding a section to
forklift-pallet.yml
with a mapping between required pallets and objects declaring a list of objects, each one declaring a list of files/patterns/globs to include or exclude (depending on the object type) from the respective pallet, with the lists applied in order (so that a list of exclusions declared after a list of inclusions will cause the exclusions to override any overlapping inclusions).This option would be simple and transparent, and it would make
forklift-pallet.yml
be a single source of truth for all file imports from required pallets. For users to selectively narrow the inclusion of files from a required pallet, they could just include all the files from it and then exclude particular paths which might conflict with other required pallets. I think we should take this approach, at least for basic use-cases.However, we should think about whether/how this design could enable certain files imported from required pallets to be saved to other target locations (e.g. to rename a deployment). For example, maybe we have
source-root
andtarget-root
fields so that files insource-root
can be imported intotarget-root
instead. Then the inclusion/exclusion blobs would be given relative tosource-root
.As an alternative option, we could store these imports in
requirements/pallets/{pallet path}/forklift-pallet-imports.yml
files. However, I am not sure what (unintended) consequences might result from importing another pallet'srequirements
directory.Enabling a pallet to declare feature flags, each of which declares a list of files which other pallets can include. This would parallel the way Forklift packages can declare feature flags. We would probably just declare feature flags in
forklift-pallet.yml
, and then we could add a YAML file into/requirements/pallets/{pallet path}/forklift-pallet-features.yml
to select feature flags to enable from the respective required pallet. This makes it easier to declare a public interface from a pallet with pre-defined groups of files which we want to encourage people to reuse (instead of forcing them to reverse-engineer inclusion patterns from inspecting the pallet's file structure), so we might want to use this approach for cleaner composability anyways.However, this adds a layer of indirection which makes it harder to figure out which files come from which required pallets. We could mitigate this by automatically generating file import manifests for the required pallets...almost like a checksum, but human-readable. These could be stored as
/requirements/pallets/{pallet path}/forklift-pallet-imports-lock.yml
which are recomputed (alongside a checksum file to be added by composition: Authenticate pallet requirements #243) whenever we change the correspondingforklift-version-lock.yml
orforklift-pallet-features.yml
files, for easy inspection without theforklift
tool. Or, since we may want to change imports using a text editor without forklift, these could beforklift-pallet-imports-manifest.yml
files which are auto-generated for inspection but gitignore'd for version control.We would probably want to enable any files provided by the pallet to override files imported at the same paths from required pallets. This would make it easy to import all files from some other pallet, and then just override a particular package deployment declaration.
We also need to figure out how to ensure that we safely handle transitive requirements among pallets (especially if we are able to include
forklift-includes.yml
files from other pallets), how we can prevent circular dependencies, and how we can deal with conflicting files among different pallets. For example, the simplest way to prevent file import conflicts among required pallets is by prohibiting a pallet from importing a non-directory file to the same target path from multiple distinct pallets - instead, the pallet must select which pallet that file will be imported from.To merge all the layered pallets together, we'd probably want to make an merge filesystem which we can use as the underlay for the pallet as an overlay filesystem. We might want to export the resulting overlay filesystem separately as
merged-pallet
in the staged pallet bundle.The text was updated successfully, but these errors were encountered: