Skip to content
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

feat: Multiple postinstall symlinks pointing to the same thing #495

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

pmengelbert
Copy link
Contributor

What this PR does / why we need it:
Currently, you can't create multiple symlink newpaths that point to the same oldpath. This is due to the fact that map keys have to be unique.

For more information, see #489

This should be merged before #494

Allow for multiple links to point to the same target.

Signed-off-by: Peter Engelbert <[email protected]>
@pmengelbert pmengelbert requested a review from a team as a code owner January 9, 2025 22:40
spec.go Outdated
@@ -130,7 +130,8 @@ type PostInstall struct {
// SymlinkTarget specifies the properties of a symlink
type SymlinkTarget struct {
// Path is the path where the symlink should be placed
Path string `yaml:"path" json:"path" jsonschema:"required"`
Path string `yaml:"path" json:"path" jsonschema:"oneof_required=path"`
Copy link
Member

@cpuguy83 cpuguy83 Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mark this is deprecated and have people use paths instead.
We can also make it so when we load the spec we migrate Path to Paths then there's only one field to deal with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we mark it as deprecated?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, but I'm going to add tests for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added.

target.go Outdated
@@ -42,6 +42,12 @@ func (t *Target) validate() error {
}
}

if t.Image != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a validate to t.Image, which itself could return nil if *Image is nil (note: this is what I did in #492

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Upon loading the spec, if `Path` is nonempty, move it to `Paths`. This
commit also reworks the tests.

Signed-off-by: Peter Engelbert <[email protected]>
@pmengelbert
Copy link
Contributor Author

@cpuguy83 This should be ready for another round of review.

@pmengelbert pmengelbert requested a review from cpuguy83 January 13, 2025 18:27
load.go Outdated

var errs []error
if err := i.Post.validate(); err != nil {
errs = append(errs, errors.Wrap(err, "postinstall"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, just "post"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

load.go Outdated

// By this point, both the oldpath and the SymlinkTarget are
// well-formed. We still need to make sure each 'newpath' is unique.
if cfg.Paths == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validations should not be modifying anything.
We can also iterate on Paths without having to have it be non-nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

load.go Outdated
cfg.Paths = []string{}
}

newpaths := slices.Clone(cfg.Paths)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the clone? Should not need to modify anything here.
If we want to make sure that cfg.Path is not also somewhere in cfg.Paths you can check that directly.

for _, link := range symlinks {
  if link.Path == cfg.Path {
    // error
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have sworn I put this in fillDefaults. I need to move this code there. The clone from moving the old code around. I'll take another pass

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I remember. Clone so we don't modify the original, but stick the cfg.Path field on the end so we can just loop through. It's weird, I'll change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the clone? Should not need to modify anything here. If we want to make sure that cfg.Path is not also somewhere in cfg.Paths you can check that directly.

for _, link := range symlinks {
  if link.Path == cfg.Path {
    // error
  }
}

And the goal is to make sure the there are no duplicate oldpaths in the whole map[string]SymlinkTarget. The desired behavior is unclear (follow the symlink that has already been created? Or overwrite the existing symlink?) Map iteration is not reliably ordered (and we sort the keys to avoid cache misses), so the user is unlikely to get what they want anyway. Better to just not allow it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. This exhibits the desired behavior, doesn't modify the spec, is tested properly, and only allocates the map when needed.

load.go Outdated

func validateSymlinks(symlinks map[string]SymlinkTarget) error {
var errs []error
dests := make(map[string]string, len(symlinks)<<1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe lets call this seen and only allocate the map if its needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@pmengelbert pmengelbert force-pushed the pmengelbert/multiple_postinstall_symlinks_pointing_to_the_same_thing/1 branch from 08fa341 to e4a8974 Compare January 16, 2025 16:41
* no longer modifies anything in `.validate()`
* only allocates map when it's actually needed
* stipulates in the test that `.Paths` should end up sorted (this is for
  llb caching)
* s/postinstall/post

Signed-off-by: Peter Engelbert <[email protected]>
@pmengelbert pmengelbert force-pushed the pmengelbert/multiple_postinstall_symlinks_pointing_to_the_same_thing/1 branch from e4a8974 to cf1cf76 Compare January 16, 2025 16:48
s = s.File(llb.Mkdir(path.Dir(dstPath), 0755, llb.WithParents(true)))
s = s.File(llb.Copy(s, srcPath, dstPath))

for oldpath, newpaths := range post.Symlinks {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need to sort this.

for src, tgt := range post.Symlinks {
fmt.Fprintf(buf, "mkdir -p %q\n", filepath.Join(rootfsPath, filepath.Dir(tgt.Path)))
fmt.Fprintf(buf, "ln -s %q %q\n", src, filepath.Join(rootfsPath, tgt.Path))
for oldpath, newpaths := range post.Symlinks {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also sort this

Post: &PostInstall{
Symlinks: map[string]SymlinkTarget{
"oldpath": {
Path: "/newpath3",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to test this since its not a valid case.

cfg.Path = ""
}

sort.Strings(cfg.Paths)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be sorted since this should be left to the user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants