diff --git a/archive/tar.go b/archive/tar.go index 6246691aa..ac04e3e02 100644 --- a/archive/tar.go +++ b/archive/tar.go @@ -114,6 +114,9 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int return 0, errors.Wrap(err, "failed to apply option") } } + if options.Filter == nil { + options.Filter = all + } return apply(ctx, root, tar.NewReader(r), options) } @@ -155,6 +158,14 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) + accept, err := options.Filter(hdr) + if err != nil { + return 0, err + } + if !accept { + continue + } + if skipFile(hdr) { log.G(ctx).Warnf("file %q ignored: archive may not be supported on system", hdr.Name) continue diff --git a/archive/tar_opts.go b/archive/tar_opts.go index b0f86abdf..a08bc102a 100644 --- a/archive/tar_opts.go +++ b/archive/tar_opts.go @@ -16,5 +16,23 @@ package archive +import "archive/tar" + // ApplyOpt allows setting mutable archive apply properties on creation type ApplyOpt func(options *ApplyOptions) error + +// Filter specific files from the archive +type Filter func(*tar.Header) (bool, error) + +// all allows all files +func all(_ *tar.Header) (bool, error) { + return true, nil +} + +// WithFilter uses the filter to select which files are to be extracted. +func WithFilter(f Filter) ApplyOpt { + return func(options *ApplyOptions) error { + options.Filter = f + return nil + } +} diff --git a/archive/tar_opts_unix.go b/archive/tar_opts_unix.go index e19afddd2..173826967 100644 --- a/archive/tar_opts_unix.go +++ b/archive/tar_opts_unix.go @@ -20,4 +20,5 @@ package archive // ApplyOptions provides additional options for an Apply operation type ApplyOptions struct { + Filter Filter // Filter tar headers } diff --git a/archive/tar_opts_windows.go b/archive/tar_opts_windows.go index 0991ab094..e4b15a163 100644 --- a/archive/tar_opts_windows.go +++ b/archive/tar_opts_windows.go @@ -22,6 +22,7 @@ package archive type ApplyOptions struct { ParentLayerPaths []string // Parent layer paths used for Windows layer apply IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer + Filter Filter // Filter tar headers } // WithParentLayers adds parent layers to the apply process this is required diff --git a/cmd/containerd/builtins.go b/cmd/containerd/builtins.go index 17fa9f629..b120b6078 100644 --- a/cmd/containerd/builtins.go +++ b/cmd/containerd/builtins.go @@ -30,6 +30,7 @@ import ( _ "github.com/containerd/containerd/services/introspection" _ "github.com/containerd/containerd/services/leases" _ "github.com/containerd/containerd/services/namespaces" + _ "github.com/containerd/containerd/services/opt" _ "github.com/containerd/containerd/services/snapshots" _ "github.com/containerd/containerd/services/tasks" _ "github.com/containerd/containerd/services/version" diff --git a/cmd/ctr/app/main.go b/cmd/ctr/app/main.go index 37ecf8707..9c33216b3 100644 --- a/cmd/ctr/app/main.go +++ b/cmd/ctr/app/main.go @@ -24,6 +24,7 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands/content" "github.com/containerd/containerd/cmd/ctr/commands/events" "github.com/containerd/containerd/cmd/ctr/commands/images" + "github.com/containerd/containerd/cmd/ctr/commands/install" "github.com/containerd/containerd/cmd/ctr/commands/leases" namespacesCmd "github.com/containerd/containerd/cmd/ctr/commands/namespaces" "github.com/containerd/containerd/cmd/ctr/commands/plugins" @@ -103,6 +104,7 @@ containerd CLI run.Command, snapshots.Command, tasks.Command, + install.Command, }, extraCmds...) app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { diff --git a/cmd/ctr/commands/install/install.go b/cmd/ctr/commands/install/install.go new file mode 100644 index 000000000..4113418fb --- /dev/null +++ b/cmd/ctr/commands/install/install.go @@ -0,0 +1,61 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package install + +import ( + "github.com/containerd/containerd" + "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/urfave/cli" +) + +// Command to install binary packages +var Command = cli.Command{ + Name: "install", + Usage: "install a new package", + ArgsUsage: "", + Description: "install a new package", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "libs,l", + Usage: "install libs from the image", + }, + cli.BoolFlag{ + Name: "replace,r", + Usage: "replace any binaries or libs in the opt directory", + }, + }, + Action: func(context *cli.Context) error { + client, ctx, cancel, err := commands.NewClient(context) + if err != nil { + return err + } + defer cancel() + ref := context.Args().First() + image, err := client.GetImage(ctx, ref) + if err != nil { + return err + } + var opts []containerd.InstallOpts + if context.Bool("libs") { + opts = append(opts, containerd.WithInstallLibs) + } + if context.Bool("replace") { + opts = append(opts, containerd.WithInstallReplace) + } + return client.Install(ctx, image, opts...) + }, +} diff --git a/install.go b/install.go new file mode 100644 index 000000000..2aa8b0394 --- /dev/null +++ b/install.go @@ -0,0 +1,91 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containerd + +import ( + "archive/tar" + "context" + "os" + "path/filepath" + + introspectionapi "github.com/containerd/containerd/api/services/introspection/v1" + "github.com/containerd/containerd/archive" + "github.com/containerd/containerd/archive/compression" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" + "github.com/pkg/errors" +) + +// Install a binary image into the opt service +func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error { + resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{ + Filters: []string{ + "id==opt", + }, + }) + if err != nil { + return err + } + if len(resp.Plugins) != 1 { + return errors.New("opt service not enabled") + } + path := resp.Plugins[0].Exports["path"] + if path == "" { + return errors.New("opt path not exported") + } + var config InstallConfig + for _, o := range opts { + o(&config) + } + var ( + cs = image.ContentStore() + platform = platforms.Default() + ) + manifest, err := images.Manifest(ctx, cs, image.Target(), platform) + if err != nil { + return err + } + for _, layer := range manifest.Layers { + ra, err := cs.ReaderAt(ctx, layer) + if err != nil { + return err + } + cr := content.NewReader(ra) + r, err := compression.DecompressStream(cr) + if err != nil { + return err + } + defer r.Close() + if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) { + d := filepath.Dir(hdr.Name) + result := d == "bin" + if config.Libs { + result = result || d == "lib" + } + if result && !config.Replace { + if _, err := os.Lstat(filepath.Join(path, hdr.Name)); err == nil { + return false, errors.Errorf("cannot replace %s in %s", hdr.Name, path) + } + } + return result, nil + })); err != nil { + return err + } + } + return nil +} diff --git a/install_opts.go b/install_opts.go new file mode 100644 index 000000000..b11e7f3d6 --- /dev/null +++ b/install_opts.go @@ -0,0 +1,38 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containerd + +// InstallOpts configures binary installs +type InstallOpts func(*InstallConfig) + +// InstallConfig sets the binary install configuration +type InstallConfig struct { + // Libs installs libs from the image + Libs bool + // Replace will overwrite existing binaries or libs in the opt directory + Replace bool +} + +// WithInstallLibs installs libs from the image +func WithInstallLibs(c *InstallConfig) { + c.Libs = true +} + +// WithInstallReplace will replace existing files +func WithInstallReplace(c *InstallConfig) { + c.Replace = true +} diff --git a/services/opt/path_unix.go b/services/opt/path_unix.go new file mode 100644 index 000000000..b4d996cad --- /dev/null +++ b/services/opt/path_unix.go @@ -0,0 +1,21 @@ +// +build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +const defaultPath = "/opt/containerd" diff --git a/services/opt/path_windows.go b/services/opt/path_windows.go new file mode 100644 index 000000000..d379920f2 --- /dev/null +++ b/services/opt/path_windows.go @@ -0,0 +1,25 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +import ( + "path/filepath" + + "github.com/containerd/containerd/defaults" +) + +var defaultPath = filepath.Join(defaults.DefaultRootDir, "opt") diff --git a/services/opt/service.go b/services/opt/service.go new file mode 100644 index 000000000..756a05f0a --- /dev/null +++ b/services/opt/service.go @@ -0,0 +1,68 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/containerd/containerd/plugin" + "github.com/pkg/errors" +) + +// Config for the opt manager +type Config struct { + // Path for the opt directory + Path string `toml:"path"` +} + +func init() { + plugin.Register(&plugin.Registration{ + Type: plugin.InternalPlugin, + ID: "opt", + Config: &Config{ + Path: defaultPath, + }, + InitFn: func(ic *plugin.InitContext) (interface{}, error) { + path := ic.Config.(*Config).Path + ic.Meta.Exports["path"] = path + + bin := filepath.Join(path, "bin") + if err := os.MkdirAll(bin, 0711); err != nil { + return nil, err + } + if err := os.Setenv("PATH", fmt.Sprintf("%s:%s", bin, os.Getenv("PATH"))); err != nil { + return nil, errors.Wrapf(err, "set binary image directory in path %s", bin) + } + if runtime.GOOS != "windows" { + lib := filepath.Join(path, "lib") + if err := os.MkdirAll(lib, 0711); err != nil { + return nil, err + } + if err := os.Setenv("LD_LIBRARY_PATH", fmt.Sprintf("%s:%s", os.Getenv("LD_LIBRARY_PATH"), lib)); err != nil { + return nil, errors.Wrapf(err, "set binary lib directory in path %s", lib) + } + } + return &manager{}, nil + }, + }) +} + +type manager struct { +} diff --git a/vendor.conf b/vendor.conf index 65b5f6973..99f3b7b16 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,4 +1,4 @@ -github.com/containerd/go-runc 14606eb66abd9e834e3bd22a4f5f46a3aad54c54 +github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031 github.com/containerd/console 4d8a41f4ce5b9bae77c41786ea2458330f43f081 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 diff --git a/vendor/github.com/containerd/go-runc/command_linux.go b/vendor/github.com/containerd/go-runc/command_linux.go index d97400d06..71b52f9de 100644 --- a/vendor/github.com/containerd/go-runc/command_linux.go +++ b/vendor/github.com/containerd/go-runc/command_linux.go @@ -18,6 +18,7 @@ package runc import ( "context" + "os" "os/exec" "syscall" ) @@ -31,6 +32,7 @@ func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: r.Setpgid, } + cmd.Env = os.Environ() if r.PdeathSignal != 0 { cmd.SysProcAttr.Pdeathsig = r.PdeathSignal } diff --git a/vendor/github.com/containerd/go-runc/command_other.go b/vendor/github.com/containerd/go-runc/command_other.go index 5209a14fb..b8fd4b866 100644 --- a/vendor/github.com/containerd/go-runc/command_other.go +++ b/vendor/github.com/containerd/go-runc/command_other.go @@ -20,6 +20,7 @@ package runc import ( "context" + "os" "os/exec" ) @@ -28,5 +29,7 @@ func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { if command == "" { command = DefaultCommand } - return exec.CommandContext(context, command, append(r.args(), args...)...) + cmd := exec.CommandContext(context, command, append(r.args(), args...)...) + cmd.Env = os.Environ() + return cmd }