From eb8c95c8a0c9f030c2f5b14adcba74e83c679a7f Mon Sep 17 00:00:00 2001 From: Peter Engelbert <36644727+pmengelbert@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:33:35 -0400 Subject: [PATCH] Enable libexec artifacts (#408) * Enable libexec artifacts This commit allows the user to specify artifacts to be installed under `usr/libexec`. I used the following documentation as a guide: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s07.html#:~:text=Purpose,subdirectory%20under%20%2Fusr%2Flibexec%20. Below, you can see at a glance what the behavior of various settings will be. The following examples assume we are building a spec with top-level `name: docker`. The subpath field defaults to the package name. ```yaml # goes to /usr/libexec/docker/docker-compose libexec: bin/docker-compose: # goes to /usr/libexec/docker/cli-plugins/docker-compose libexec: bin/docker-compose: subpath: docker/cli-plugins # goes to /usr/libexec/something_else/cli-plugins/docker-compose libexec: bin/docker-compose: subpath: something_else/cli-plugins # goes to /usr/libexec/docker/cli-plugins/hello libexec: bin/docker-compose: subpath: docker/cli-plugins name: hello # goes to /usr/libexec/docker/hello libexec: bin/docker-compose: name: hello ``` Signed-off-by: Peter Engelbert --- artifacts.go | 2 + docs/spec.schema.json | 7 +++ frontend/deb/debroot.go | 10 ++++ frontend/rpm/template.go | 18 +++++++ test/azlinux_test.go | 104 ++++++++++++++++++++++++++++++++++++++ website/docs/artifacts.md | 26 ++++++++++ 6 files changed, 167 insertions(+) diff --git a/artifacts.go b/artifacts.go index f19b8677a..93592a487 100644 --- a/artifacts.go +++ b/artifacts.go @@ -12,6 +12,8 @@ import ( type Artifacts struct { // Binaries is the list of binaries to include in the package. Binaries map[string]ArtifactConfig `yaml:"binaries,omitempty" json:"binaries,omitempty"` + // Libexec is the list of additional binaries that may be invoked by the main package binary. + Libexec map[string]ArtifactConfig `yaml:"libexec,omitempty" json:"libexec,omitempty"` // Manpages is the list of manpages to include in the package. Manpages map[string]ArtifactConfig `yaml:"manpages,omitempty" json:"manpages,omitempty"` // DataDirs is a list of read-only architecture-independent data files, to be placed in /usr/share/ diff --git a/docs/spec.schema.json b/docs/spec.schema.json index c1bd16065..5ee7e32f4 100644 --- a/docs/spec.schema.json +++ b/docs/spec.schema.json @@ -76,6 +76,13 @@ "type": "object", "description": "Binaries is the list of binaries to include in the package." }, + "libexec": { + "additionalProperties": { + "$ref": "#/$defs/ArtifactConfig" + }, + "type": "object", + "description": "Libexec is the list of additional binaries that may be invoked by the main package binary." + }, "manpages": { "additionalProperties": { "$ref": "#/$defs/ArtifactConfig" diff --git a/frontend/deb/debroot.go b/frontend/deb/debroot.go index 483998862..24aea4782 100644 --- a/frontend/deb/debroot.go +++ b/frontend/deb/debroot.go @@ -452,6 +452,16 @@ func createInstallScripts(worker llb.State, spec *dalec.Spec, dir string) []llb. } } + if len(spec.Artifacts.Libexec) > 0 { + sorted := dalec.SortMapKeys(spec.Artifacts.Libexec) + for _, key := range sorted { + cfg := spec.Artifacts.Libexec[key] + resolved := cfg.ResolveName(key) + targetDir := filepath.Join(`/usr/libexec`, cfg.SubPath) + writeInstall(key, targetDir, resolved) + } + } + if len(spec.Artifacts.Libs) > 0 { sorted := dalec.SortMapKeys(spec.Artifacts.Libs) for _, key := range sorted { diff --git a/frontend/rpm/template.go b/frontend/rpm/template.go index a446308d5..80e6a7aea 100644 --- a/frontend/rpm/template.go +++ b/frontend/rpm/template.go @@ -499,6 +499,14 @@ func (w *specWrapper) Install() fmt.Stringer { } } + if w.Spec.Artifacts.Libexec != nil { + libexecFileKeys := dalec.SortMapKeys(w.Spec.Artifacts.Libexec) + for _, k := range libexecFileKeys { + le := w.Spec.Artifacts.Libexec[k] + copyArtifact(`%{buildroot}/%{_libexecdir}`, k, &le) + } + } + configKeys := dalec.SortMapKeys(w.Spec.Artifacts.ConfigFiles) for _, c := range configKeys { cfg := w.Spec.Artifacts.ConfigFiles[c] @@ -599,6 +607,16 @@ func (w *specWrapper) Files() fmt.Stringer { } } + if w.Spec.Artifacts.Libexec != nil { + dataKeys := dalec.SortMapKeys(w.Spec.Artifacts.Libexec) + for _, k := range dataKeys { + le := w.Spec.Artifacts.Libexec[k] + targetDir := filepath.Join(`%{_libexecdir}`, le.SubPath) + fullPath := filepath.Join(targetDir, le.ResolveName(k)) + fmt.Fprintln(b, fullPath) + } + } + configKeys := dalec.SortMapKeys(w.Spec.Artifacts.ConfigFiles) for _, c := range configKeys { cfg := w.Spec.Artifacts.ConfigFiles[c] diff --git a/test/azlinux_test.go b/test/azlinux_test.go index ce17940cb..3baeb313b 100644 --- a/test/azlinux_test.go +++ b/test/azlinux_test.go @@ -1022,6 +1022,110 @@ Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/boot }) }) + t.Run("test libexec file installation", func(t *testing.T) { + t.Parallel() + spec := &dalec.Spec{ + Name: "libexec-test", + Version: "0.0.1", + Revision: "1", + License: "MIT", + Website: "https://github.com/azure/dalec", + Vendor: "Dalec", + Packager: "Dalec", + Description: "Should install specified data files", + Sources: map[string]dalec.Source{ + "no_name_no_subpath": { + Inline: &dalec.SourceInline{ + File: &dalec.SourceInlineFile{ + Contents: "#!/usr/bin/env bash\necho hello world", + Permissions: 0o755, + }, + }, + }, + "name_only": { + Inline: &dalec.SourceInline{ + File: &dalec.SourceInlineFile{ + Contents: "#!/usr/bin/env bash\necho hello world", + Permissions: 0o755, + }, + }, + }, + "name_and_subpath": { + Inline: &dalec.SourceInline{ + File: &dalec.SourceInlineFile{ + Contents: "#!/usr/bin/env bash\necho hello world", + Permissions: 0o755, + }, + }, + }, + "subpath_only": { + Inline: &dalec.SourceInline{ + File: &dalec.SourceInlineFile{ + Contents: "#!/usr/bin/env bash\necho hello world", + Permissions: 0o755, + }, + }, + }, + "nested_subpath": { + Inline: &dalec.SourceInline{ + File: &dalec.SourceInlineFile{ + Contents: "#!/usr/bin/env bash\necho hello world", + Permissions: 0o755, + }, + }, + }, + }, + Build: dalec.ArtifactBuild{}, + Artifacts: dalec.Artifacts{ + Binaries: map[string]dalec.ArtifactConfig{ + "no_name_no_subpath": {}, + }, + Libexec: map[string]dalec.ArtifactConfig{ + "no_name_no_subpath": {}, + "name_only": { + Name: "this_is_the_name_only", + }, + "name_and_subpath": { + SubPath: "subpath", + Name: "custom_name", + }, + "subpath_only": dalec.ArtifactConfig{ + SubPath: "custom", + }, + "nested_subpath": dalec.ArtifactConfig{ + SubPath: "libexec-test/abcdefg", + }, + }, + }, + } + + testEnv.RunTest(ctx, t, func(ctx context.Context, client gwclient.Client) { + req := newSolveRequest(withBuildTarget(testConfig.Target.Container), withSpec(ctx, t, spec)) + res := solveT(ctx, t, client, req) + + ref, err := res.SingleRef() + if err != nil { + t.Fatal(err) + } + + if err := validatePathAndPermissions(ctx, ref, "/usr/libexec/no_name_no_subpath", 0o755); err != nil { + t.Fatal(err) + } + if err := validatePathAndPermissions(ctx, ref, "/usr/libexec/this_is_the_name_only", 0o755); err != nil { + t.Fatal(err) + } + if err := validatePathAndPermissions(ctx, ref, "/usr/libexec/subpath/custom_name", 0o755); err != nil { + t.Fatal(err) + } + if err := validatePathAndPermissions(ctx, ref, "/usr/libexec/custom/subpath_only", 0o755); err != nil { + t.Fatal(err) + } + if err := validatePathAndPermissions(ctx, ref, "/usr/libexec/libexec-test/abcdefg/nested_subpath", 0o755); err != nil { + t.Fatal(err) + } + }) + }) + t.Run("test config files handled", func(t *testing.T) { t.Parallel() spec := &dalec.Spec{ diff --git a/website/docs/artifacts.md b/website/docs/artifacts.md index a7869a1b7..85ba0a29b 100644 --- a/website/docs/artifacts.md +++ b/website/docs/artifacts.md @@ -45,6 +45,32 @@ artifacts: You may use a trailing wildcard to specify multiple binaries in a directory, though behavior may differ between different OS's/distros. +### Libexec + +Libexec files are additional executable files that may be executed by one of +the main package executables. On Linux these would typically get installed into +`/usr/libexec/` or `/usr/libexec/`. + +Files under libexec are a mapping of file path to [artifact configuration](#artifact-configuration). +If `subpath` is not supplied, the artifact will be installed in `/usr/libexec` +directly. The file path is the path to a file that must be available after the +build section has finished. This path is relative to the working directory of +the build phase *before* any directory changes are made. + +Example: + +```yaml +name: my_package + +artifacts: + # the following config will install my_bin at /usr/libexec/my package/my_bin + libexec: + src/my_bin: +``` + +You may use a trailing wildcard to specify multiple binaries in a directory, +though behavior may differ between different OS's/distros. + ### Manpages Manpages is short for manual pages.