diff --git a/archive/tar.go b/archive/tar.go index 6246691aa..9eeea7816 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,10 @@ 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) + if !options.Filter(hdr) { + 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..a0ee39a03 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 + +// all allows all files +func all(_ *tar.Header) bool { + return true +} + +// 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..ad0cb5862 --- /dev/null +++ b/cmd/ctr/commands/install/install.go @@ -0,0 +1,43 @@ +/* + 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/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", + 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 + } + return client.Install(ctx, image) + }, +} diff --git a/install.go b/install.go new file mode 100644 index 000000000..7fa967aca --- /dev/null +++ b/install.go @@ -0,0 +1,76 @@ +/* + 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" + "errors" + "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" +) + +// Install a binary image into the opt service +func (c *Client) Install(ctx context.Context, image Image) 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 ( + 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 { + return filepath.Dir(hdr.Name) == "bin" + })); err != nil { + return err + } + } + return nil +} 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..ce07cc94e --- /dev/null +++ b/services/opt/service.go @@ -0,0 +1,58 @@ +/* + 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" + + "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) + } + return &manager{}, nil + }, + }) +} + +type manager struct { +}