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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1077,16 +1077,38 @@
"description": "Spec is the specification for a package build."
},
"SymlinkTarget": {
"oneOf": [
{
"required": [
"path"
],
"title": "path"
},
{
"required": [
"paths"
],
"title": "paths"
}
],
"properties": {
"path": {
"type": "string",
"description": "Path is the path where the symlink should be placed"
"description": "Path is the path where the symlink should be placed\n\nDeprecated: This is here for backward compatibility. Use `Paths` instead."
},
"paths": {
"items": {
"type": "string"
},
"type": "array",
"description": "Path is a list of `newpath`s that will all point to the same `oldpath`."
}
},
"additionalProperties": false,
"type": "object",
"required": [
"path"
"path",
"paths"
],
"description": "SymlinkTarget specifies the properties of a symlink"
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/azlinux/handle_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func specToContainerLLB(w worker, spec *dalec.Spec, targetKey string, rpmDir llb
dalec.WithConstraints(opts...),
).AddMount(workPath, rootfs)

if post := spec.GetImagePost(targetKey); post != nil && len(post.Symlinks) > 0 {
if post := spec.GetImagePost(targetKey); post != nil {
rootfs = builderImg.
Run(dalec.WithConstraints(opts...), dalec.InstallPostSymlinks(post, workPath)).
AddMount(workPath, rootfs)
Expand Down
16 changes: 7 additions & 9 deletions frontend/windows/handle_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,19 @@ func copySymlinks(post *dalec.PostInstall) llb.StateOption {
return s
}

lm := post.Symlinks
if len(lm) == 0 {
if len(post.Symlinks) == 0 {
return s
}
keys := dalec.SortMapKeys(lm)
for _, srcPath := range keys {
l := lm[srcPath]
dstPath := l.Path
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 _, newpath := range newpaths.Paths {
s = s.File(llb.Mkdir(path.Dir(newpath), 0755, llb.WithParents(true)))
s = s.File(llb.Copy(s, oldpath, newpath))
}
}

return s
}

}

func getTargetPlatform(bc *dockerui.Client) (ocispecs.Platform, error) {
Expand Down
8 changes: 5 additions & 3 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,11 @@ func InstallPostSymlinks(post *PostInstall, rootfsPath string) llb.RunOption {
buf := bytes.NewBuffer(nil)
buf.WriteString("set -ex\n")

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

for _, newpath := range newpaths.Paths {
fmt.Fprintf(buf, "mkdir -p %q\n", filepath.Join(rootfsPath, filepath.Dir(newpath)))
fmt.Fprintf(buf, "ln -s %q %q\n", oldpath, filepath.Join(rootfsPath, newpath))
}
}

const name = "tmp.dalec.symlink.sh"
Expand Down
111 changes: 111 additions & 0 deletions load.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ func (s *Spec) FillDefaults() {
t.fillDefaults()
s.Targets[k] = t
}

s.Image.fillDefaults()
}

func (s Spec) Validate() error {
Expand Down Expand Up @@ -381,8 +383,13 @@ func (s Spec) Validate() error {
}
}

if err := s.Image.validate(); err != nil {
errs = append(errs, errors.Wrap(err, "image"))
}

return goerrors.Join(errs...)
}

func validatePatch(patch PatchSpec, patchSrc Source) error {
if SourceIsDir(patchSrc) {
// Patch sources that use directory-backed sources require a subpath in the
Expand Down Expand Up @@ -447,3 +454,107 @@ func (b *ArtifactBuild) processBuildArgs(lex *shell.Lex, args map[string]string,

return goerrors.Join(errs...)
}

func (i *ImageConfig) validate() error {
if i == nil {
return nil
}

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

return goerrors.Join(errs...)
}

func (p *PostInstall) validate() error {
if p == nil {
return nil
}

var errs []error

if err := validateSymlinks(p.Symlinks); err != nil {
errs = append(errs, err)
}

return errors.Wrap(goerrors.Join(errs...), "symlink")
}

func validateSymlinks(symlinks map[string]SymlinkTarget) error {
var (
errs []error
numPairs int
)

for oldpath, cfg := range symlinks {
var err error
if oldpath == "" {
err = fmt.Errorf("symlink source is empty")
errs = append(errs, err)
}

if cfg.Path != "" && len(cfg.Paths) != 0 || cfg.Path == "" && len(cfg.Paths) == 0 {
err = fmt.Errorf("'path' and 'paths' fields are mutually exclusive, and at least one is required: "+
"symlink to %s", oldpath)

errs = append(errs, err)
}

if err != nil {
continue
}

if cfg.Path != "" { // this means .Paths is empty
numPairs++
continue
}

for _, newpath := range cfg.Paths { // this means .Path is empty
numPairs++
if newpath == "" {
errs = append(errs, fmt.Errorf("symlink newpath should not be empty"))
continue
}
}
}

// The remainder of this function checks for duplicate `newpath`s in the
// symlink pairs. This is not allowed: neither the ordering of the
// `oldpath` map keys, nor that of the `.Paths` values can be trusted. We
// also sort both to avoid cache misses, so we would end up with
// inconsistent behavior -- regardless of whether the inputs are the same.
if numPairs < 2 {
return goerrors.Join(errs...)
}

var (
oldpath string
cfg SymlinkTarget
)

seen := make(map[string]string, numPairs)
checkDuplicateNewpath := func(newpath string) {
if newpath == "" {
return
}

if seenPath, found := seen[newpath]; found {
errs = append(errs, fmt.Errorf("symlink 'newpaths' must be unique: %q points to both %q and %q",
newpath, oldpath, seenPath))
}

seen[newpath] = oldpath
}

for oldpath, cfg = range symlinks {
checkDuplicateNewpath(cfg.Path)

for _, newpath := range cfg.Paths {
checkDuplicateNewpath(newpath)
}
}

return goerrors.Join(errs...)
}
Loading
Loading