Update containerd to 3c1ef1a714
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
12
vendor/github.com/containerd/containerd/client.go
generated
vendored
12
vendor/github.com/containerd/containerd/client.go
generated
vendored
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/containerd/containerd/events"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
@@ -236,6 +235,10 @@ type RemoteContext struct {
|
||||
// If no resolver is provided, defaults to Docker registry resolver.
|
||||
Resolver remotes.Resolver
|
||||
|
||||
// Platforms defines which platforms to handle when doing the image operation.
|
||||
// If this field is empty, content for all platforms will be pulled.
|
||||
Platforms []string
|
||||
|
||||
// Unpack is done after an image is pulled to extract into a snapshotter.
|
||||
// If an image is not unpacked on pull, it can be unpacked any time
|
||||
// afterwards. Unpacking is required to run an image.
|
||||
@@ -287,6 +290,7 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
|
||||
}
|
||||
|
||||
fetcher, err := pullCtx.Resolver.Fetcher(ctx, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get fetcher for %q", name)
|
||||
@@ -304,8 +308,8 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image
|
||||
childrenHandler := images.ChildrenHandler(store)
|
||||
// Set any children labels for that content
|
||||
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
||||
// Filter the childen by the platform
|
||||
childrenHandler = images.FilterPlatform(platforms.Default(), childrenHandler)
|
||||
// Filter childen by platforms
|
||||
childrenHandler = images.FilterPlatforms(childrenHandler, pullCtx.Platforms...)
|
||||
|
||||
handler = images.Handlers(append(pullCtx.BaseHandlers,
|
||||
remotes.FetchHandler(store, fetcher),
|
||||
@@ -371,7 +375,7 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
|
||||
return err
|
||||
}
|
||||
|
||||
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.BaseHandlers...)
|
||||
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.Platforms, pushCtx.BaseHandlers...)
|
||||
}
|
||||
|
||||
// GetImage returns an existing image
|
||||
|
||||
15
vendor/github.com/containerd/containerd/client_opts.go
generated
vendored
15
vendor/github.com/containerd/containerd/client_opts.go
generated
vendored
@@ -64,6 +64,21 @@ func WithServices(opts ...ServicesOpt) ClientOpt {
|
||||
// RemoteOpt allows the caller to set distribution options for a remote
|
||||
type RemoteOpt func(*Client, *RemoteContext) error
|
||||
|
||||
// WithPlatform allows the caller to specify a platform to retrieve
|
||||
// content for
|
||||
func WithPlatform(platform string) RemoteOpt {
|
||||
return func(_ *Client, c *RemoteContext) error {
|
||||
for _, p := range c.Platforms {
|
||||
if p == platform {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
c.Platforms = append(c.Platforms, platform)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPullUnpack is used to unpack an image after pull. This
|
||||
// uses the snapshotter, content store, and diff service
|
||||
// configured for the client.
|
||||
|
||||
113
vendor/github.com/containerd/containerd/cmd/ctr/command/main.go
generated
vendored
Normal file
113
vendor/github.com/containerd/containerd/cmd/ctr/command/main.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/containers"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/content"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/events"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/images"
|
||||
namespacesCmd "github.com/containerd/containerd/cmd/ctr/commands/namespaces"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/pprof"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/snapshots"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
|
||||
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
|
||||
"github.com/containerd/containerd/defaults"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
var extraCmds = []cli.Command{}
|
||||
|
||||
func init() {
|
||||
// Discard grpc logs so that they don't mess with our stdio
|
||||
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
|
||||
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
fmt.Println(c.App.Name, version.Package, c.App.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// App returns a *cli.App instance.
|
||||
func App() *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.Name = "ctr"
|
||||
app.Version = version.Version
|
||||
app.Usage = `
|
||||
__
|
||||
_____/ /______
|
||||
/ ___/ __/ ___/
|
||||
/ /__/ /_/ /
|
||||
\___/\__/_/
|
||||
|
||||
containerd CLI
|
||||
`
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output in logs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address for containerd's GRPC server",
|
||||
Value: defaults.DefaultAddress,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Usage: "total timeout for ctr commands",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "connect-timeout",
|
||||
Usage: "timeout for connecting to containerd",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "namespace, n",
|
||||
Usage: "namespace to use with commands",
|
||||
Value: namespaces.Default,
|
||||
EnvVar: namespaces.NamespaceEnvVar,
|
||||
},
|
||||
}
|
||||
app.Commands = append([]cli.Command{
|
||||
plugins.Command,
|
||||
versionCmd.Command,
|
||||
containers.Command,
|
||||
content.Command,
|
||||
events.Command,
|
||||
images.Command,
|
||||
namespacesCmd.Command,
|
||||
pprof.Command,
|
||||
run.Command,
|
||||
snapshots.Command,
|
||||
tasks.Command,
|
||||
}, extraCmds...)
|
||||
app.Before = func(context *cli.Context) error {
|
||||
if context.GlobalBool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return app
|
||||
}
|
||||
25
vendor/github.com/containerd/containerd/cmd/ctr/command/main_unix.go
generated
vendored
Normal file
25
vendor/github.com/containerd/containerd/cmd/ctr/command/main_unix.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// +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 command
|
||||
|
||||
import "github.com/containerd/containerd/cmd/ctr/commands/shim"
|
||||
|
||||
func init() {
|
||||
extraCmds = append(extraCmds, shim.Command)
|
||||
}
|
||||
56
vendor/github.com/containerd/containerd/cmd/ctr/commands/client.go
generated
vendored
Normal file
56
vendor/github.com/containerd/containerd/cmd/ctr/commands/client.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// AppContext returns the context for a command. Should only be called once per
|
||||
// command, near the start.
|
||||
//
|
||||
// This will ensure the namespace is picked up and set the timeout, if one is
|
||||
// defined.
|
||||
func AppContext(context *cli.Context) (gocontext.Context, gocontext.CancelFunc) {
|
||||
var (
|
||||
ctx = gocontext.Background()
|
||||
timeout = context.GlobalDuration("timeout")
|
||||
namespace = context.GlobalString("namespace")
|
||||
cancel gocontext.CancelFunc
|
||||
)
|
||||
ctx = namespaces.WithNamespace(ctx, namespace)
|
||||
if timeout > 0 {
|
||||
ctx, cancel = gocontext.WithTimeout(ctx, timeout)
|
||||
} else {
|
||||
ctx, cancel = gocontext.WithCancel(ctx)
|
||||
}
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
// NewClient returns a new containerd client
|
||||
func NewClient(context *cli.Context) (*containerd.Client, gocontext.Context, gocontext.CancelFunc, error) {
|
||||
client, err := containerd.New(context.GlobalString("address"))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
ctx, cancel := AppContext(context)
|
||||
return client, ctx, cancel, nil
|
||||
}
|
||||
121
vendor/github.com/containerd/containerd/cmd/ctr/commands/commands.go
generated
vendored
Normal file
121
vendor/github.com/containerd/containerd/cmd/ctr/commands/commands.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
// SnapshotterFlags are cli flags specifying snapshotter names
|
||||
SnapshotterFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "snapshotter",
|
||||
Usage: "snapshotter name. Empty value stands for the default value.",
|
||||
Value: containerd.DefaultSnapshotter,
|
||||
EnvVar: "CONTAINERD_SNAPSHOTTER",
|
||||
},
|
||||
}
|
||||
|
||||
// LabelFlag is a cli flag specifying labels
|
||||
LabelFlag = cli.StringSliceFlag{
|
||||
Name: "label",
|
||||
Usage: "labels to attach to the image",
|
||||
}
|
||||
|
||||
// RegistryFlags are cli flags specifying registry options
|
||||
RegistryFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "skip-verify,k",
|
||||
Usage: "skip SSL certificate validation",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "plain-http",
|
||||
Usage: "allow connections using plain HTTP",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user,u",
|
||||
Usage: "user[:password] Registry user and password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "refresh",
|
||||
Usage: "refresh token for authorization server",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// ObjectWithLabelArgs returns the first arg and a LabelArgs object
|
||||
func ObjectWithLabelArgs(clicontext *cli.Context) (string, map[string]string) {
|
||||
var (
|
||||
first = clicontext.Args().First()
|
||||
labelStrings = clicontext.Args().Tail()
|
||||
)
|
||||
|
||||
return first, LabelArgs(labelStrings)
|
||||
}
|
||||
|
||||
// LabelArgs returns a map of label key,value pairs
|
||||
func LabelArgs(labelStrings []string) map[string]string {
|
||||
labels := make(map[string]string, len(labelStrings))
|
||||
for _, label := range labelStrings {
|
||||
parts := strings.SplitN(label, "=", 2)
|
||||
key := parts[0]
|
||||
value := "true"
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
|
||||
labels[key] = value
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
// PrintAsJSON prints input in JSON format
|
||||
func PrintAsJSON(x interface{}) {
|
||||
b, err := json.MarshalIndent(x, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "can't marshal %+v as a JSON string: %v\n", x, err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
|
||||
// WritePidFile writes the pid atomically to a file
|
||||
func WritePidFile(path string, pid int) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
|
||||
f, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(f, "%d", pid)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tempPath, path)
|
||||
}
|
||||
257
vendor/github.com/containerd/containerd/cmd/ctr/commands/containers/containers.go
generated
vendored
Normal file
257
vendor/github.com/containerd/containerd/cmd/ctr/commands/containers/containers.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
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 containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is the cli command for managing containers
|
||||
var Command = cli.Command{
|
||||
Name: "containers",
|
||||
Usage: "manage containers",
|
||||
Aliases: []string{"c", "container"},
|
||||
Subcommands: []cli.Command{
|
||||
createCommand,
|
||||
deleteCommand,
|
||||
infoCommand,
|
||||
listCommand,
|
||||
setLabelsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var createCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create container",
|
||||
ArgsUsage: "[flags] Image|RootFS CONTAINER",
|
||||
Flags: append(commands.SnapshotterFlags, run.ContainerFlags...),
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
id = context.Args().Get(1)
|
||||
ref = context.Args().First()
|
||||
)
|
||||
if ref == "" {
|
||||
return errors.New("image ref must be provided")
|
||||
}
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
_, err = run.NewContainer(ctx, client, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list containers",
|
||||
ArgsUsage: "[flags] [<filter>, ...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the container id",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
filters = context.Args()
|
||||
quiet = context.Bool("quiet")
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
containers, err := client.Containers(ctx, filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if quiet {
|
||||
for _, c := range containers {
|
||||
fmt.Printf("%s\n", c.ID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
|
||||
for _, c := range containers {
|
||||
info, err := c.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageName := info.Image
|
||||
if imageName == "" {
|
||||
imageName = "-"
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n",
|
||||
c.ID(),
|
||||
imageName,
|
||||
info.Runtime.Name,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "delete one or more existing containers",
|
||||
ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
|
||||
Aliases: []string{"del", "rm"},
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "keep-snapshot",
|
||||
Usage: "do not clean up snapshot with container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var exitErr error
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
deleteOpts := []containerd.DeleteOpts{}
|
||||
if !context.Bool("keep-snapshot") {
|
||||
deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup)
|
||||
}
|
||||
|
||||
if context.NArg() == 0 {
|
||||
return errors.New("must specify at least one container to delete")
|
||||
}
|
||||
for _, arg := range context.Args() {
|
||||
if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil {
|
||||
if exitErr == nil {
|
||||
exitErr = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg)
|
||||
}
|
||||
}
|
||||
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
|
||||
func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error {
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return container.Delete(ctx, opts...)
|
||||
}
|
||||
status, err := task.Status(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status.Status == containerd.Stopped || status.Status == containerd.Created {
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return container.Delete(ctx, opts...)
|
||||
}
|
||||
return fmt.Errorf("cannot delete a non stopped container: %v", status)
|
||||
|
||||
}
|
||||
|
||||
var setLabelsCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "set and clear labels for a container",
|
||||
ArgsUsage: "[flags] CONTAINER [<key>=<value>, ...]",
|
||||
Description: "set and clear labels for a container",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(context *cli.Context) error {
|
||||
containerID, labels := commands.ObjectWithLabelArgs(context)
|
||||
if containerID == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
container, err := client.LoadContainer(ctx, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setlabels, err := container.SetLabels(ctx, labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range setlabels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var infoCommand = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "get info about a container",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := container.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commands.PrintAsJSON(info)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
560
vendor/github.com/containerd/containerd/cmd/ctr/commands/content/content.go
generated
vendored
Normal file
560
vendor/github.com/containerd/containerd/cmd/ctr/commands/content/content.go
generated
vendored
Normal file
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
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 content
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
units "github.com/docker/go-units"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
// Command is the cli command for managing content
|
||||
Command = cli.Command{
|
||||
Name: "content",
|
||||
Usage: "manage content",
|
||||
Subcommands: cli.Commands{
|
||||
activeIngestCommand,
|
||||
deleteCommand,
|
||||
editCommand,
|
||||
fetchCommand,
|
||||
fetchObjectCommand,
|
||||
getCommand,
|
||||
ingestCommand,
|
||||
listCommand,
|
||||
pushObjectCommand,
|
||||
setLabelsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
getCommand = cli.Command{
|
||||
Name: "get",
|
||||
Usage: "get the data for an object",
|
||||
ArgsUsage: "[<digest>, ...]",
|
||||
Description: "display the image object",
|
||||
Action: func(context *cli.Context) error {
|
||||
dgst, err := digest.Parse(context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
cs := client.ContentStore()
|
||||
ra, err := cs.ReaderAt(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
_, err = io.Copy(os.Stdout, content.NewReader(ra))
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
ingestCommand = cli.Command{
|
||||
Name: "ingest",
|
||||
Usage: "accept content into the store",
|
||||
ArgsUsage: "[flags] <key>",
|
||||
Description: "ingest objects into the local content store",
|
||||
Flags: []cli.Flag{
|
||||
cli.Int64Flag{
|
||||
Name: "expected-size",
|
||||
Usage: "validate against provided size",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "expected-digest",
|
||||
Usage: "verify content against expected digest",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
expectedSize = context.Int64("expected-size")
|
||||
expectedDigest = digest.Digest(context.String("expected-digest"))
|
||||
)
|
||||
if err := expectedDigest.Validate(); expectedDigest != "" && err != nil {
|
||||
return err
|
||||
}
|
||||
if ref == "" {
|
||||
return errors.New("must specify a transaction reference")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
cs := client.ContentStore()
|
||||
|
||||
// TODO(stevvooe): Allow ingest to be reentrant. Currently, we expect
|
||||
// all data to be written in a single invocation. Allow multiple writes
|
||||
// to the same transaction key followed by a commit.
|
||||
return content.WriteBlob(ctx, cs, ref, os.Stdin, expectedSize, expectedDigest)
|
||||
},
|
||||
}
|
||||
|
||||
activeIngestCommand = cli.Command{
|
||||
Name: "active",
|
||||
Usage: "display active transfers",
|
||||
ArgsUsage: "[flags] [<regexp>]",
|
||||
Description: "display the ongoing transfers",
|
||||
Flags: []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "timeout, t",
|
||||
Usage: "total timeout for fetch",
|
||||
EnvVar: "CONTAINERD_FETCH_TIMEOUT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "root",
|
||||
Usage: "path to content store root",
|
||||
Value: "/tmp/content", // TODO(stevvooe): for now, just use the PWD/.content
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
match := context.Args().First()
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
cs := client.ContentStore()
|
||||
active, err := cs.ListStatuses(ctx, match)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
fmt.Fprintln(tw, "REF\tSIZE\tAGE\t")
|
||||
for _, active := range active {
|
||||
fmt.Fprintf(tw, "%s\t%s\t%s\t\n",
|
||||
active.Ref,
|
||||
units.HumanSize(float64(active.Offset)),
|
||||
units.HumanDuration(time.Since(active.StartedAt)))
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list all blobs in the store",
|
||||
ArgsUsage: "[flags]",
|
||||
Description: "list blobs in the content store",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the blob digest",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
quiet = context.Bool("quiet")
|
||||
args = []string(context.Args())
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
cs := client.ContentStore()
|
||||
|
||||
var walkFn content.WalkFunc
|
||||
if quiet {
|
||||
walkFn = func(info content.Info) error {
|
||||
fmt.Println(info.Digest)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||
defer tw.Flush()
|
||||
|
||||
fmt.Fprintln(tw, "DIGEST\tSIZE\tAGE\tLABELS")
|
||||
walkFn = func(info content.Info) error {
|
||||
var labelStrings []string
|
||||
for k, v := range info.Labels {
|
||||
labelStrings = append(labelStrings, strings.Join([]string{k, v}, "="))
|
||||
}
|
||||
labels := strings.Join(labelStrings, ",")
|
||||
if labels == "" {
|
||||
labels = "-"
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n",
|
||||
info.Digest,
|
||||
units.HumanSize(float64(info.Size)),
|
||||
units.HumanDuration(time.Since(info.CreatedAt)),
|
||||
labels)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return cs.Walk(ctx, walkFn, args...)
|
||||
},
|
||||
}
|
||||
|
||||
setLabelsCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "add labels to content",
|
||||
ArgsUsage: "<digest> [<label>=<value> ...]",
|
||||
Description: "labels blobs in the content store",
|
||||
Action: func(context *cli.Context) error {
|
||||
object, labels := commands.ObjectWithLabelArgs(context)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
cs := client.ContentStore()
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := content.Info{
|
||||
Digest: dgst,
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for k, v := range labels {
|
||||
paths = append(paths, fmt.Sprintf("labels.%s", k))
|
||||
if v != "" {
|
||||
info.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing updated, do no clear
|
||||
if len(paths) == 0 {
|
||||
info, err = cs.Info(ctx, info.Digest)
|
||||
} else {
|
||||
info, err = cs.Update(ctx, info, paths...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range info.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
editCommand = cli.Command{
|
||||
Name: "edit",
|
||||
Usage: "edit a blob and return a new digest",
|
||||
ArgsUsage: "[flags] <digest>",
|
||||
Description: "edit a blob and return a new digest",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "validate",
|
||||
Usage: "validate the result against a format (json, mediatype, etc.)",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
validate = context.String("validate")
|
||||
object = context.Args().First()
|
||||
)
|
||||
|
||||
if validate != "" {
|
||||
return errors.New("validating the edit result not supported")
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Support looking up objects by a reference through
|
||||
// the image metadata storage.
|
||||
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
cs := client.ContentStore()
|
||||
ra, err := cs.ReaderAt(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
nrc, err := edit(content.NewReader(ra))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nrc.Close()
|
||||
|
||||
wr, err := cs.Writer(ctx, "edit-"+object, 0, "") // TODO(stevvooe): Choose a better key?
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(wr, nrc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wr.Commit(ctx, 0, wr.Digest()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(wr.Digest())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"del", "remove", "rm"},
|
||||
Usage: "permanently delete one or more blobs",
|
||||
ArgsUsage: "[<digest>, ...]",
|
||||
Description: `Delete one or more blobs permanently. Successfully deleted
|
||||
blobs are printed to stdout.`,
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
args = []string(context.Args())
|
||||
exitError error
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
cs := client.ContentStore()
|
||||
|
||||
for _, arg := range args {
|
||||
dgst, err := digest.Parse(arg)
|
||||
if err != nil {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cs.Delete(ctx, dgst); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitError == nil {
|
||||
exitError = err
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("could not delete %v", dgst)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(dgst)
|
||||
}
|
||||
|
||||
return exitError
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Create "multi-fetch" mode that just takes a remote
|
||||
// then receives object/hint lines on stdin, returning content as
|
||||
// needed.
|
||||
fetchObjectCommand = cli.Command{
|
||||
Name: "fetch-object",
|
||||
Usage: "retrieve objects from a remote",
|
||||
ArgsUsage: "[flags] <remote> <object> [<hint>, ...]",
|
||||
Description: `Fetch objects by identifier from a remote.`,
|
||||
Flags: commands.RegistryFlags,
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
)
|
||||
ctx, cancel := commands.AppContext(context)
|
||||
defer cancel()
|
||||
|
||||
resolver, err := commands.GetResolver(ctx, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("ref", ref))
|
||||
|
||||
log.G(ctx).Debugf("resolving")
|
||||
name, desc, err := resolver.Resolve(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fetcher, err := resolver.Fetcher(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).Debugf("fetching")
|
||||
rc, err := fetcher.Fetch(ctx, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_, err = io.Copy(os.Stdout, rc)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
pushObjectCommand = cli.Command{
|
||||
Name: "push-object",
|
||||
Usage: "push an object to a remote",
|
||||
ArgsUsage: "[flags] <remote> <object> <type>",
|
||||
Description: `Push objects by identifier to a remote.`,
|
||||
Flags: commands.RegistryFlags,
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().Get(0)
|
||||
object = context.Args().Get(1)
|
||||
media = context.Args().Get(2)
|
||||
)
|
||||
dgst, err := digest.Parse(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
resolver, err := commands.GetResolver(ctx, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("ref", ref))
|
||||
|
||||
log.G(ctx).Debugf("resolving")
|
||||
pusher, err := resolver.Pusher(ctx, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := client.ContentStore()
|
||||
|
||||
info, err := cs.Info(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desc := ocispec.Descriptor{
|
||||
MediaType: media,
|
||||
Digest: dgst,
|
||||
Size: info.Size,
|
||||
}
|
||||
|
||||
ra, err := cs.ReaderAt(ctx, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
cw, err := pusher.Push(ctx, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Progress reader
|
||||
if err := content.Copy(ctx, cw, content.NewReader(ra), desc.Size, desc.Digest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Pushed %s %s\n", desc.Digest, desc.MediaType)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func edit(rd io.Reader) (io.ReadCloser, error) {
|
||||
tmp, err := ioutil.TempFile("", "edit-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tmp, rd); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", "$EDITOR "+tmp.Name())
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := tmp.Seek(0, io.SeekStart); err != nil {
|
||||
tmp.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return onCloser{ReadCloser: tmp, onClose: func() error {
|
||||
return os.RemoveAll(tmp.Name())
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type onCloser struct {
|
||||
io.ReadCloser
|
||||
onClose func() error
|
||||
}
|
||||
|
||||
func (oc onCloser) Close() error {
|
||||
var err error
|
||||
if err1 := oc.ReadCloser.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
if oc.onClose != nil {
|
||||
err1 := oc.onClose()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
340
vendor/github.com/containerd/containerd/cmd/ctr/commands/content/fetch.go
generated
vendored
Normal file
340
vendor/github.com/containerd/containerd/cmd/ctr/commands/content/fetch.go
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
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 content
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/progress"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var fetchCommand = cli.Command{
|
||||
Name: "fetch",
|
||||
Usage: "fetch all content for an image into containerd",
|
||||
ArgsUsage: "[flags] <remote> <object>",
|
||||
Description: `Fetch an image into containerd.
|
||||
|
||||
This command ensures that containerd has all the necessary resources to build
|
||||
an image's rootfs and convert the configuration to a runtime format supported
|
||||
by containerd.
|
||||
|
||||
This command uses the same syntax, of remote and object, as 'ctr fetch-object'.
|
||||
We may want to make this nicer, but agnostism is preferred for the moment.
|
||||
|
||||
Right now, the responsibility of the daemon and the cli aren't quite clear. Do
|
||||
not use this implementation as a guide. The end goal should be having metadata,
|
||||
content and snapshots ready for a direct use via the 'ctr run'.
|
||||
|
||||
Most of this is experimental and there are few leaps to make this work.`,
|
||||
Flags: append(commands.RegistryFlags, commands.LabelFlag),
|
||||
Action: func(clicontext *cli.Context) error {
|
||||
var (
|
||||
ref = clicontext.Args().First()
|
||||
)
|
||||
_, err := Fetch(ref, clicontext)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// Fetch loads all resources into the content store and returns the image
|
||||
func Fetch(ref string, cliContext *cli.Context) (containerd.Image, error) {
|
||||
client, ctx, cancel, err := commands.NewClient(cliContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
resolver, err := commands.GetResolver(ctx, cliContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ongoing := newJobs(ref)
|
||||
|
||||
pctx, stopProgress := context.WithCancel(ctx)
|
||||
progress := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
if !cliContext.GlobalBool("debug") {
|
||||
// no progress bar, because it hides some debug logs
|
||||
showProgress(pctx, ongoing, client.ContentStore(), os.Stdout)
|
||||
}
|
||||
close(progress)
|
||||
}()
|
||||
|
||||
h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
if desc.MediaType != images.MediaTypeDockerSchema1Manifest {
|
||||
ongoing.add(desc)
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
log.G(pctx).WithField("image", ref).Debug("fetching")
|
||||
labels := commands.LabelArgs(cliContext.StringSlice("label"))
|
||||
opts := []containerd.RemoteOpt{
|
||||
containerd.WithPullLabels(labels),
|
||||
containerd.WithResolver(resolver),
|
||||
containerd.WithImageHandler(h),
|
||||
containerd.WithSchema1Conversion,
|
||||
}
|
||||
|
||||
if !cliContext.Bool("all-platforms") {
|
||||
for _, platform := range cliContext.StringSlice("platform") {
|
||||
opts = append(opts, containerd.WithPlatform(platform))
|
||||
}
|
||||
}
|
||||
|
||||
img, err := client.Pull(pctx, ref, opts...)
|
||||
stopProgress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
<-progress
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func showProgress(ctx context.Context, ongoing *jobs, cs content.Store, out io.Writer) {
|
||||
var (
|
||||
ticker = time.NewTicker(100 * time.Millisecond)
|
||||
fw = progress.NewWriter(out)
|
||||
start = time.Now()
|
||||
statuses = map[string]StatusInfo{}
|
||||
done bool
|
||||
)
|
||||
defer ticker.Stop()
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
fw.Flush()
|
||||
|
||||
tw := tabwriter.NewWriter(fw, 1, 8, 1, ' ', 0)
|
||||
|
||||
resolved := "resolved"
|
||||
if !ongoing.isResolved() {
|
||||
resolved = "resolving"
|
||||
}
|
||||
statuses[ongoing.name] = StatusInfo{
|
||||
Ref: ongoing.name,
|
||||
Status: resolved,
|
||||
}
|
||||
keys := []string{ongoing.name}
|
||||
|
||||
activeSeen := map[string]struct{}{}
|
||||
if !done {
|
||||
active, err := cs.ListStatuses(ctx, "")
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Error("active check failed")
|
||||
continue
|
||||
}
|
||||
// update status of active entries!
|
||||
for _, active := range active {
|
||||
statuses[active.Ref] = StatusInfo{
|
||||
Ref: active.Ref,
|
||||
Status: "downloading",
|
||||
Offset: active.Offset,
|
||||
Total: active.Total,
|
||||
StartedAt: active.StartedAt,
|
||||
UpdatedAt: active.UpdatedAt,
|
||||
}
|
||||
activeSeen[active.Ref] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// now, update the items in jobs that are not in active
|
||||
for _, j := range ongoing.jobs() {
|
||||
key := remotes.MakeRefKey(ctx, j)
|
||||
keys = append(keys, key)
|
||||
if _, ok := activeSeen[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
status, ok := statuses[key]
|
||||
if !done && (!ok || status.Status == "downloading") {
|
||||
info, err := cs.Info(ctx, j.Digest)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
log.G(ctx).WithError(err).Errorf("failed to get content info")
|
||||
continue outer
|
||||
} else {
|
||||
statuses[key] = StatusInfo{
|
||||
Ref: key,
|
||||
Status: "waiting",
|
||||
}
|
||||
}
|
||||
} else if info.CreatedAt.After(start) {
|
||||
statuses[key] = StatusInfo{
|
||||
Ref: key,
|
||||
Status: "done",
|
||||
Offset: info.Size,
|
||||
Total: info.Size,
|
||||
UpdatedAt: info.CreatedAt,
|
||||
}
|
||||
} else {
|
||||
statuses[key] = StatusInfo{
|
||||
Ref: key,
|
||||
Status: "exists",
|
||||
}
|
||||
}
|
||||
} else if done {
|
||||
if ok {
|
||||
if status.Status != "done" && status.Status != "exists" {
|
||||
status.Status = "done"
|
||||
statuses[key] = status
|
||||
}
|
||||
} else {
|
||||
statuses[key] = StatusInfo{
|
||||
Ref: key,
|
||||
Status: "done",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ordered []StatusInfo
|
||||
for _, key := range keys {
|
||||
ordered = append(ordered, statuses[key])
|
||||
}
|
||||
|
||||
Display(tw, ordered, start)
|
||||
tw.Flush()
|
||||
|
||||
if done {
|
||||
fw.Flush()
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
done = true // allow ui to update once more
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// jobs provides a way of identifying the download keys for a particular task
|
||||
// encountering during the pull walk.
|
||||
//
|
||||
// This is very minimal and will probably be replaced with something more
|
||||
// featured.
|
||||
type jobs struct {
|
||||
name string
|
||||
added map[digest.Digest]struct{}
|
||||
descs []ocispec.Descriptor
|
||||
mu sync.Mutex
|
||||
resolved bool
|
||||
}
|
||||
|
||||
func newJobs(name string) *jobs {
|
||||
return &jobs{
|
||||
name: name,
|
||||
added: map[digest.Digest]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jobs) add(desc ocispec.Descriptor) {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
j.resolved = true
|
||||
|
||||
if _, ok := j.added[desc.Digest]; ok {
|
||||
return
|
||||
}
|
||||
j.descs = append(j.descs, desc)
|
||||
j.added[desc.Digest] = struct{}{}
|
||||
}
|
||||
|
||||
func (j *jobs) jobs() []ocispec.Descriptor {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
var descs []ocispec.Descriptor
|
||||
return append(descs, j.descs...)
|
||||
}
|
||||
|
||||
func (j *jobs) isResolved() bool {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
return j.resolved
|
||||
}
|
||||
|
||||
// StatusInfo holds the status info for an upload or download
|
||||
type StatusInfo struct {
|
||||
Ref string
|
||||
Status string
|
||||
Offset int64
|
||||
Total int64
|
||||
StartedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Display pretty prints out the download or upload progress
|
||||
func Display(w io.Writer, statuses []StatusInfo, start time.Time) {
|
||||
var total int64
|
||||
for _, status := range statuses {
|
||||
total += status.Offset
|
||||
switch status.Status {
|
||||
case "downloading", "uploading":
|
||||
var bar progress.Bar
|
||||
if status.Total > 0.0 {
|
||||
bar = progress.Bar(float64(status.Offset) / float64(status.Total))
|
||||
}
|
||||
fmt.Fprintf(w, "%s:\t%s\t%40r\t%8.8s/%s\t\n",
|
||||
status.Ref,
|
||||
status.Status,
|
||||
bar,
|
||||
progress.Bytes(status.Offset), progress.Bytes(status.Total))
|
||||
case "resolving", "waiting":
|
||||
bar := progress.Bar(0.0)
|
||||
fmt.Fprintf(w, "%s:\t%s\t%40r\t\n",
|
||||
status.Ref,
|
||||
status.Status,
|
||||
bar)
|
||||
default:
|
||||
bar := progress.Bar(1.0)
|
||||
fmt.Fprintf(w, "%s:\t%s\t%40r\t\n",
|
||||
status.Ref,
|
||||
status.Status,
|
||||
bar)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "elapsed: %-4.1fs\ttotal: %7.6v\t(%v)\t\n",
|
||||
time.Since(start).Seconds(),
|
||||
// TODO(stevvooe): These calculations are actually way off.
|
||||
// Need to account for previously downloaded data. These
|
||||
// will basically be right for a download the first time
|
||||
// but will be skewed if restarting, as it includes the
|
||||
// data into the start time before.
|
||||
progress.Bytes(total),
|
||||
progress.NewBytesPerSecond(total, time.Since(start)))
|
||||
}
|
||||
79
vendor/github.com/containerd/containerd/cmd/ctr/commands/events/events.go
generated
vendored
Normal file
79
vendor/github.com/containerd/containerd/cmd/ctr/commands/events/events.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/events"
|
||||
"github.com/containerd/typeurl"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
// Register grpc event types
|
||||
_ "github.com/containerd/containerd/api/events"
|
||||
)
|
||||
|
||||
// Command is the cli command for displaying containerd events
|
||||
var Command = cli.Command{
|
||||
Name: "events",
|
||||
Aliases: []string{"event"},
|
||||
Usage: "display containerd events",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
eventsClient := client.EventService()
|
||||
eventsCh, errCh := eventsClient.Subscribe(ctx, context.Args()...)
|
||||
for {
|
||||
var e *events.Envelope
|
||||
select {
|
||||
case evt, closed := <-eventsCh:
|
||||
if closed {
|
||||
return nil
|
||||
}
|
||||
e = evt
|
||||
case err := <-errCh:
|
||||
return err
|
||||
}
|
||||
|
||||
var out []byte
|
||||
if e.Event != nil {
|
||||
v, err := typeurl.UnmarshalAny(e.Event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Println(
|
||||
e.Timestamp,
|
||||
e.Namespace,
|
||||
e.Topic,
|
||||
string(out),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
125
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/export.go
generated
vendored
Normal file
125
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/export.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 images
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
oci "github.com/containerd/containerd/images/oci"
|
||||
"github.com/containerd/containerd/reference"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var exportCommand = cli.Command{
|
||||
Name: "export",
|
||||
Usage: "export an image",
|
||||
ArgsUsage: "[flags] <out> <image>",
|
||||
Description: `Export an image to a tar stream.
|
||||
Currently, only OCI format is supported.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
// TODO(AkihiroSuda): make this map[string]string as in moby/moby#33355?
|
||||
cli.StringFlag{
|
||||
Name: "oci-ref-name",
|
||||
Value: "",
|
||||
Usage: "override org.opencontainers.image.ref.name annotation",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Usage: "digest of manifest",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest-type",
|
||||
Usage: "media type of manifest digest",
|
||||
Value: ocispec.MediaTypeImageManifest,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
out = context.Args().First()
|
||||
local = context.Args().Get(1)
|
||||
desc ocispec.Descriptor
|
||||
)
|
||||
if out == "" || local == "" {
|
||||
return errors.New("please provide both an output filename and an image reference to export")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if manifest := context.String("manifest"); manifest != "" {
|
||||
desc.Digest, err = digest.Parse(manifest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid manifest digest")
|
||||
}
|
||||
desc.MediaType = context.String("manifest-type")
|
||||
} else {
|
||||
img, err := client.ImageService().Get(ctx, local)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to resolve image to manifest")
|
||||
}
|
||||
desc = img.Target
|
||||
}
|
||||
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = make(map[string]string)
|
||||
}
|
||||
if s, ok := desc.Annotations[ocispec.AnnotationRefName]; !ok || s == "" {
|
||||
if ociRefName := determineOCIRefName(local); ociRefName != "" {
|
||||
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
|
||||
}
|
||||
if ociRefName := context.String("oci-ref-name"); ociRefName != "" {
|
||||
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
|
||||
}
|
||||
}
|
||||
var w io.WriteCloser
|
||||
if out == "-" {
|
||||
w = os.Stdout
|
||||
} else {
|
||||
w, err = os.Create(out)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
r, err := client.Export(ctx, &oci.V1Exporter{}, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Close()
|
||||
},
|
||||
}
|
||||
|
||||
func determineOCIRefName(local string) string {
|
||||
refspec, err := reference.Parse(local)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
tag, _ := reference.SplitObject(refspec.Object)
|
||||
return tag
|
||||
}
|
||||
330
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/images.go
generated
vendored
Normal file
330
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/images.go
generated
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
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 images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/progress"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is the cli command for managing images
|
||||
var Command = cli.Command{
|
||||
Name: "images",
|
||||
Aliases: []string{"image", "i"},
|
||||
Usage: "manage images",
|
||||
Subcommands: cli.Commands{
|
||||
checkCommand,
|
||||
exportCommand,
|
||||
importCommand,
|
||||
listCommand,
|
||||
pullCommand,
|
||||
pushCommand,
|
||||
removeCommand,
|
||||
setLabelsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list images known to containerd",
|
||||
ArgsUsage: "[flags] <ref>",
|
||||
Description: "list images registered with containerd",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the image refs",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
filters = context.Args()
|
||||
quiet = context.Bool("quiet")
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
imageStore = client.ImageService()
|
||||
cs = client.ContentStore()
|
||||
)
|
||||
imageList, err := imageStore.List(ctx, filters...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list images")
|
||||
}
|
||||
if quiet {
|
||||
for _, image := range imageList {
|
||||
fmt.Println(image.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tPLATFORMS\tLABELS\t")
|
||||
for _, image := range imageList {
|
||||
size, err := image.Size(ctx, cs, platforms.Default())
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("failed calculating size for image %s", image.Name)
|
||||
}
|
||||
|
||||
platformColumn := "-"
|
||||
specs, err := images.Platforms(ctx, cs, image.Target)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("failed resolving platform for image %s", image.Name)
|
||||
} else if len(specs) > 0 {
|
||||
psm := map[string]struct{}{}
|
||||
for _, p := range specs {
|
||||
psm[platforms.Format(p)] = struct{}{}
|
||||
}
|
||||
var ps []string
|
||||
for p := range psm {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
sort.Stable(sort.StringSlice(ps))
|
||||
platformColumn = strings.Join(ps, ",")
|
||||
}
|
||||
|
||||
labels := "-"
|
||||
if len(image.Labels) > 0 {
|
||||
var pairs []string
|
||||
for k, v := range image.Labels {
|
||||
pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
labels = strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%s\t\n",
|
||||
image.Name,
|
||||
image.Target.MediaType,
|
||||
image.Target.Digest,
|
||||
progress.Bytes(size),
|
||||
platformColumn,
|
||||
labels)
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var setLabelsCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "set and clear labels for an image",
|
||||
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
||||
Description: "set and clear labels for an image",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "replace-all, r",
|
||||
Usage: "replace all labels",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
replaceAll = context.Bool("replace-all")
|
||||
name, labels = commands.ObjectWithLabelArgs(context)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if name == "" {
|
||||
return errors.New("please specify an image")
|
||||
}
|
||||
|
||||
var (
|
||||
is = client.ImageService()
|
||||
fieldpaths []string
|
||||
)
|
||||
|
||||
for k := range labels {
|
||||
if replaceAll {
|
||||
fieldpaths = append(fieldpaths, "labels")
|
||||
} else {
|
||||
fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, "."))
|
||||
}
|
||||
}
|
||||
|
||||
image := images.Image{
|
||||
Name: name,
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
updated, err := is.Update(ctx, image, fieldpaths...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range updated.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var checkCommand = cli.Command{
|
||||
Name: "check",
|
||||
Usage: "check that an image has all content available locally",
|
||||
ArgsUsage: "[flags] <ref> [<ref>, ...]",
|
||||
Description: "check that an image has all content available locally",
|
||||
Flags: commands.SnapshotterFlags,
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
exitErr error
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
contentStore = client.ContentStore()
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
)
|
||||
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\tUNPACKED\t")
|
||||
|
||||
args := []string(context.Args())
|
||||
imageList, err := client.ListImages(ctx, args...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed listing images")
|
||||
}
|
||||
|
||||
for _, image := range imageList {
|
||||
var (
|
||||
status string = "complete"
|
||||
size string
|
||||
requiredSize int64
|
||||
presentSize int64
|
||||
)
|
||||
|
||||
available, required, present, missing, err := images.Check(ctx, contentStore, image.Target(), platforms.Default())
|
||||
if err != nil {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to check %v", image.Name())
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to check %v", image.Name())
|
||||
status = "error"
|
||||
}
|
||||
|
||||
if status != "error" {
|
||||
for _, d := range required {
|
||||
requiredSize += d.Size
|
||||
}
|
||||
|
||||
for _, d := range present {
|
||||
presentSize += d.Size
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
status = "incomplete"
|
||||
}
|
||||
|
||||
if available {
|
||||
status += fmt.Sprintf(" (%v/%v)", len(present), len(required))
|
||||
size = fmt.Sprintf("%v/%v", progress.Bytes(presentSize), progress.Bytes(requiredSize))
|
||||
} else {
|
||||
status = fmt.Sprintf("unavailable (%v/?)", len(present))
|
||||
size = fmt.Sprintf("%v/?", progress.Bytes(presentSize))
|
||||
}
|
||||
} else {
|
||||
size = "-"
|
||||
}
|
||||
|
||||
unpacked, err := image.IsUnpacked(ctx, context.String("snapshotter"))
|
||||
if err != nil {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to check unpack for %v", image.Name())
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to check unpack for %v", image.Name())
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%t\n",
|
||||
image.Name(),
|
||||
image.Target().MediaType,
|
||||
image.Target().Digest,
|
||||
status,
|
||||
size,
|
||||
unpacked)
|
||||
}
|
||||
tw.Flush()
|
||||
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
|
||||
var removeCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "remove one or more images by reference",
|
||||
ArgsUsage: "[flags] <ref> [<ref>, ...]",
|
||||
Description: "remove one or more images by reference",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "sync",
|
||||
Usage: "Synchronously remove image and all associated resources",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
exitErr error
|
||||
imageStore = client.ImageService()
|
||||
)
|
||||
for i, target := range context.Args() {
|
||||
var opts []images.DeleteOpt
|
||||
if context.Bool("sync") && i == context.NArg()-1 {
|
||||
opts = append(opts, images.SynchronousDelete())
|
||||
}
|
||||
if err := imageStore.Delete(ctx, target, opts...); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to delete %v", target)
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to delete %v", target)
|
||||
continue
|
||||
}
|
||||
// image ref not found in metadata store; log not found condition
|
||||
log.G(ctx).Warnf("%v: image not found", target)
|
||||
} else {
|
||||
fmt.Println(target)
|
||||
}
|
||||
}
|
||||
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
114
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/import.go
generated
vendored
Normal file
114
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/import.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/images"
|
||||
oci "github.com/containerd/containerd/images/oci"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var importCommand = cli.Command{
|
||||
Name: "import",
|
||||
Usage: "import images",
|
||||
ArgsUsage: "[flags] <in>",
|
||||
Description: `Import images from a tar stream.
|
||||
Implemented formats:
|
||||
- oci.v1 (default)
|
||||
|
||||
|
||||
For oci.v1 format, you need to specify --oci-name because an OCI archive contains image refs (tags)
|
||||
but does not contain the base image name.
|
||||
|
||||
e.g.
|
||||
$ ctr images import --format oci.v1 --oci-name foo/bar foobar.tar
|
||||
|
||||
If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadbeef", the command will create
|
||||
"foo/bar:latest" and "foo/bar@sha256:deadbeef" images in the containerd store.
|
||||
`,
|
||||
Flags: append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
Value: "oci.v1",
|
||||
Usage: "image format. See DESCRIPTION.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "oci-name",
|
||||
Value: "unknown/unknown",
|
||||
Usage: "prefix added to either oci.v1 ref annotation or digest",
|
||||
},
|
||||
// TODO(AkihiroSuda): support commands.LabelFlag (for all children objects)
|
||||
}, commands.SnapshotterFlags...),
|
||||
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
in = context.Args().First()
|
||||
imageImporter images.Importer
|
||||
)
|
||||
|
||||
switch format := context.String("format"); format {
|
||||
case "oci.v1":
|
||||
imageImporter = &oci.V1Importer{
|
||||
ImageName: context.String("oci-name"),
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown format %s", format)
|
||||
}
|
||||
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
var r io.ReadCloser
|
||||
if in == "-" {
|
||||
r = os.Stdin
|
||||
} else {
|
||||
r, err = os.Open(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
imgs, err := client.Import(ctx, imageImporter, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = r.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).Debugf("unpacking %d images", len(imgs))
|
||||
|
||||
for _, img := range imgs {
|
||||
// TODO: Show unpack status
|
||||
fmt.Printf("unpacking %s (%s)...", img.Name(), img.Target().Digest)
|
||||
err = img.Unpack(ctx, context.String("snapshotter"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("done")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
78
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/pull.go
generated
vendored
Normal file
78
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/pull.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/content"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var pullCommand = cli.Command{
|
||||
Name: "pull",
|
||||
Usage: "pull an image from a remote",
|
||||
ArgsUsage: "[flags] <ref>",
|
||||
Description: `Fetch and prepare an image for use in containerd.
|
||||
|
||||
After pulling an image, it should be ready to use the same reference in a run
|
||||
command. As part of this process, we do the following:
|
||||
|
||||
1. Fetch all resources into containerd.
|
||||
2. Prepare the snapshot filesystem with the pulled resources.
|
||||
3. Register metadata for the image.
|
||||
`,
|
||||
Flags: append(append(commands.RegistryFlags, append(commands.SnapshotterFlags, commands.LabelFlag)...),
|
||||
cli.StringSliceFlag{
|
||||
Name: "platform",
|
||||
Usage: "Pull content from a specific platform",
|
||||
Value: &cli.StringSlice{platforms.Default()},
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "all-platforms",
|
||||
Usage: "pull content from all platforms",
|
||||
},
|
||||
),
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
)
|
||||
if ref == "" {
|
||||
return fmt.Errorf("please provide an image reference to pull")
|
||||
}
|
||||
ctx, cancel := commands.AppContext(context)
|
||||
defer cancel()
|
||||
|
||||
img, err := content.Fetch(ref, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).WithField("image", ref).Debug("unpacking")
|
||||
|
||||
// TODO: Show unpack status
|
||||
fmt.Printf("unpacking %s...\n", img.Target().Digest)
|
||||
err = img.Unpack(ctx, context.String("snapshotter"))
|
||||
if err == nil {
|
||||
fmt.Println("done")
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
212
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/push.go
generated
vendored
Normal file
212
vendor/github.com/containerd/containerd/cmd/ctr/commands/images/push.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
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 images
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"os"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/progress"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var pushCommand = cli.Command{
|
||||
Name: "push",
|
||||
Usage: "push an image to a remote",
|
||||
ArgsUsage: "[flags] <remote> [<local>]",
|
||||
Description: `Pushes an image reference from containerd.
|
||||
|
||||
All resources associated with the manifest reference will be pushed.
|
||||
The ref is used to resolve to a locally existing image manifest.
|
||||
The image manifest must exist before push. Creating a new image
|
||||
manifest can be done through calculating the diff for layers,
|
||||
creating the associated configuration, and creating the manifest
|
||||
which references those resources.
|
||||
`,
|
||||
Flags: append(commands.RegistryFlags, cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Usage: "digest of manifest",
|
||||
}, cli.StringFlag{
|
||||
Name: "manifest-type",
|
||||
Usage: "media type of manifest digest",
|
||||
Value: ocispec.MediaTypeImageManifest,
|
||||
}),
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
local = context.Args().Get(1)
|
||||
desc ocispec.Descriptor
|
||||
)
|
||||
if ref == "" {
|
||||
return errors.New("please provide a remote image reference to push")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if manifest := context.String("manifest"); manifest != "" {
|
||||
desc.Digest, err = digest.Parse(manifest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid manifest digest")
|
||||
}
|
||||
desc.MediaType = context.String("manifest-type")
|
||||
} else {
|
||||
if local == "" {
|
||||
local = ref
|
||||
}
|
||||
img, err := client.ImageService().Get(ctx, local)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to resolve image to manifest")
|
||||
}
|
||||
desc = img.Target
|
||||
}
|
||||
|
||||
resolver, err := commands.GetResolver(ctx, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ongoing := newPushJobs(commands.PushTracker)
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
log.G(ctx).WithField("image", ref).WithField("digest", desc.Digest).Debug("pushing")
|
||||
|
||||
jobHandler := images.HandlerFunc(func(ctx gocontext.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
ongoing.add(remotes.MakeRefKey(ctx, desc))
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
return client.Push(ctx, ref, desc,
|
||||
containerd.WithResolver(resolver),
|
||||
containerd.WithImageHandler(jobHandler),
|
||||
)
|
||||
})
|
||||
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
errs <- eg.Wait()
|
||||
}()
|
||||
|
||||
var (
|
||||
ticker = time.NewTicker(100 * time.Millisecond)
|
||||
fw = progress.NewWriter(os.Stdout)
|
||||
start = time.Now()
|
||||
done bool
|
||||
)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
fw.Flush()
|
||||
|
||||
tw := tabwriter.NewWriter(fw, 1, 8, 1, ' ', 0)
|
||||
|
||||
content.Display(tw, ongoing.status(), start)
|
||||
tw.Flush()
|
||||
|
||||
if done {
|
||||
fw.Flush()
|
||||
return nil
|
||||
}
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
done = true
|
||||
case <-ctx.Done():
|
||||
done = true // allow ui to update once more
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
type pushjobs struct {
|
||||
jobs map[string]struct{}
|
||||
ordered []string
|
||||
tracker docker.StatusTracker
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newPushJobs(tracker docker.StatusTracker) *pushjobs {
|
||||
return &pushjobs{
|
||||
jobs: make(map[string]struct{}),
|
||||
tracker: tracker,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *pushjobs) add(ref string) {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
if _, ok := j.jobs[ref]; ok {
|
||||
return
|
||||
}
|
||||
j.ordered = append(j.ordered, ref)
|
||||
j.jobs[ref] = struct{}{}
|
||||
}
|
||||
|
||||
func (j *pushjobs) status() []content.StatusInfo {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
statuses := make([]content.StatusInfo, 0, len(j.jobs))
|
||||
for _, name := range j.ordered {
|
||||
si := content.StatusInfo{
|
||||
Ref: name,
|
||||
}
|
||||
|
||||
status, err := j.tracker.GetStatus(name)
|
||||
if err != nil {
|
||||
si.Status = "waiting"
|
||||
} else {
|
||||
si.Offset = status.Offset
|
||||
si.Total = status.Total
|
||||
si.StartedAt = status.StartedAt
|
||||
si.UpdatedAt = status.UpdatedAt
|
||||
if status.Offset >= status.Total {
|
||||
if status.UploadUUID == "" {
|
||||
si.Status = "done"
|
||||
} else {
|
||||
si.Status = "committing"
|
||||
}
|
||||
} else {
|
||||
si.Status = "uploading"
|
||||
}
|
||||
}
|
||||
statuses = append(statuses, si)
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
173
vendor/github.com/containerd/containerd/cmd/ctr/commands/namespaces/namespaces.go
generated
vendored
Normal file
173
vendor/github.com/containerd/containerd/cmd/ctr/commands/namespaces/namespaces.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 namespaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is the cli command for managing namespaces
|
||||
var Command = cli.Command{
|
||||
Name: "namespaces",
|
||||
Aliases: []string{"namespace"},
|
||||
Usage: "manage namespaces",
|
||||
Subcommands: cli.Commands{
|
||||
createCommand,
|
||||
listCommand,
|
||||
removeCommand,
|
||||
setLabelsCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var createCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create a new namespace",
|
||||
ArgsUsage: "<name> [<key>=<value]",
|
||||
Description: "create a new namespace. it must be unique",
|
||||
Action: func(context *cli.Context) error {
|
||||
namespace, labels := commands.ObjectWithLabelArgs(context)
|
||||
if namespace == "" {
|
||||
return errors.New("please specify a namespace")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
namespaces := client.NamespaceService()
|
||||
return namespaces.Create(ctx, namespace, labels)
|
||||
},
|
||||
}
|
||||
|
||||
var setLabelsCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "set and clear labels for a namespace",
|
||||
ArgsUsage: "<name> [<key>=<value>, ...]",
|
||||
Description: "set and clear labels for a namespace",
|
||||
Action: func(context *cli.Context) error {
|
||||
namespace, labels := commands.ObjectWithLabelArgs(context)
|
||||
if namespace == "" {
|
||||
return errors.New("please specify a namespace")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
namespaces := client.NamespaceService()
|
||||
for k, v := range labels {
|
||||
if err := namespaces.SetLabel(ctx, namespace, k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list namespaces",
|
||||
ArgsUsage: "[flags]",
|
||||
Description: "list namespaces",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the namespace name",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
quiet := context.Bool("quiet")
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
namespaces := client.NamespaceService()
|
||||
nss, err := namespaces.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if quiet {
|
||||
for _, ns := range nss {
|
||||
fmt.Println(ns)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
fmt.Fprintln(tw, "NAME\tLABELS\t")
|
||||
for _, ns := range nss {
|
||||
labels, err := namespaces.Labels(ctx, ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range labels {
|
||||
labelStrings = append(labelStrings, strings.Join([]string{k, v}, "="))
|
||||
}
|
||||
sort.Strings(labelStrings)
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\t\n", ns, strings.Join(labelStrings, ","))
|
||||
}
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var removeCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "remove one or more namespaces",
|
||||
ArgsUsage: "<name> [<name>, ...]",
|
||||
Description: "remove one or more namespaces. for now, the namespace must be empty",
|
||||
Action: func(context *cli.Context) error {
|
||||
var exitErr error
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
namespaces := client.NamespaceService()
|
||||
for _, target := range context.Args() {
|
||||
if err := namespaces.Delete(ctx, target); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to delete %v", target)
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to delete %v", target)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fmt.Println(target)
|
||||
}
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
153
vendor/github.com/containerd/containerd/cmd/ctr/commands/plugins/plugins.go
generated
vendored
Normal file
153
vendor/github.com/containerd/containerd/cmd/ctr/commands/plugins/plugins.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
introspection "github.com/containerd/containerd/api/services/introspection/v1"
|
||||
"github.com/containerd/containerd/api/types"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/urfave/cli"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// Command is a cli command that outputs plugin information
|
||||
var Command = cli.Command{
|
||||
Name: "plugins",
|
||||
Aliases: []string{"plugin"},
|
||||
Usage: "provides information about containerd plugins",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet,q",
|
||||
Usage: "print only the plugin ids",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detailed,d",
|
||||
Usage: "print detailed information about each plugin",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
quiet = context.Bool("quiet")
|
||||
detailed = context.Bool("detailed")
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
ps := client.IntrospectionService()
|
||||
response, err := ps.Plugins(ctx, &introspection.PluginsRequest{
|
||||
Filters: context.Args(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if quiet {
|
||||
for _, plugin := range response.Plugins {
|
||||
fmt.Println(plugin.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
||||
if detailed {
|
||||
first := true
|
||||
for _, plugin := range response.Plugins {
|
||||
if !first {
|
||||
fmt.Fprintln(w, "\t\t\t")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintln(w, "Type:\t", plugin.Type)
|
||||
fmt.Fprintln(w, "ID:\t", plugin.ID)
|
||||
if len(plugin.Requires) > 0 {
|
||||
fmt.Fprintln(w, "Requires:\t")
|
||||
for _, r := range plugin.Requires {
|
||||
fmt.Fprintln(w, "\t", r)
|
||||
}
|
||||
}
|
||||
if len(plugin.Platforms) > 0 {
|
||||
fmt.Fprintln(w, "Platforms:\t", prettyPlatforms(plugin.Platforms))
|
||||
}
|
||||
|
||||
if len(plugin.Exports) > 0 {
|
||||
fmt.Fprintln(w, "Exports:\t")
|
||||
for k, v := range plugin.Exports {
|
||||
fmt.Fprintln(w, "\t", k, "\t", v)
|
||||
}
|
||||
}
|
||||
|
||||
if len(plugin.Capabilities) > 0 {
|
||||
fmt.Fprintln(w, "Capabilities:\t", strings.Join(plugin.Capabilities, ","))
|
||||
}
|
||||
|
||||
if plugin.InitErr != nil {
|
||||
fmt.Fprintln(w, "Error:\t")
|
||||
fmt.Fprintln(w, "\t Code:\t", codes.Code(plugin.InitErr.Code))
|
||||
fmt.Fprintln(w, "\t Message:\t", plugin.InitErr.Message)
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "TYPE\tID\tPLATFORMS\tSTATUS\t")
|
||||
for _, plugin := range response.Plugins {
|
||||
status := "ok"
|
||||
|
||||
if plugin.InitErr != nil {
|
||||
status = "error"
|
||||
}
|
||||
|
||||
var platformColumn = "-"
|
||||
if len(plugin.Platforms) > 0 {
|
||||
platformColumn = prettyPlatforms(plugin.Platforms)
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n",
|
||||
plugin.Type,
|
||||
plugin.ID,
|
||||
platformColumn,
|
||||
status,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
func prettyPlatforms(pspb []types.Platform) string {
|
||||
psm := map[string]struct{}{}
|
||||
for _, p := range pspb {
|
||||
psm[platforms.Format(v1.Platform{
|
||||
OS: p.OS,
|
||||
Architecture: p.Architecture,
|
||||
Variant: p.Variant,
|
||||
})] = struct{}{}
|
||||
}
|
||||
var ps []string
|
||||
for p := range psm {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
sort.Stable(sort.StringSlice(ps))
|
||||
return strings.Join(ps, ",")
|
||||
}
|
||||
181
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof.go
generated
vendored
Normal file
181
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
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 pprof
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/defaults"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type pprofDialer struct {
|
||||
proto string
|
||||
addr string
|
||||
}
|
||||
|
||||
// Command is the cli command for providing golang pprof outputs for containerd
|
||||
var Command = cli.Command{
|
||||
Name: "pprof",
|
||||
Usage: "provide golang pprof outputs for containerd",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "debug-socket, d",
|
||||
Usage: "socket path for containerd's debug server",
|
||||
Value: defaults.DefaultDebugAddress,
|
||||
},
|
||||
},
|
||||
Subcommands: []cli.Command{
|
||||
pprofBlockCommand,
|
||||
pprofGoroutinesCommand,
|
||||
pprofHeapCommand,
|
||||
pprofProfileCommand,
|
||||
pprofThreadcreateCommand,
|
||||
pprofTraceCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var pprofGoroutinesCommand = cli.Command{
|
||||
Name: "goroutines",
|
||||
Usage: "dump goroutine stack dump",
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
output, err := httpGetRequest(client, "/debug/pprof/goroutine?debug=2")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var pprofHeapCommand = cli.Command{
|
||||
Name: "heap",
|
||||
Usage: "dump heap profile",
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
output, err := httpGetRequest(client, "/debug/pprof/heap")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var pprofProfileCommand = cli.Command{
|
||||
Name: "profile",
|
||||
Usage: "CPU profile",
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
output, err := httpGetRequest(client, "/debug/pprof/profile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var pprofTraceCommand = cli.Command{
|
||||
Name: "trace",
|
||||
Usage: "collect execution trace",
|
||||
Flags: []cli.Flag{
|
||||
cli.DurationFlag{
|
||||
Name: "seconds,s",
|
||||
Usage: "trace time (seconds)",
|
||||
Value: 5 * time.Second,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
seconds := context.Duration("seconds").Seconds()
|
||||
uri := fmt.Sprintf("/debug/pprof/trace?seconds=%v", seconds)
|
||||
output, err := httpGetRequest(client, uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var pprofBlockCommand = cli.Command{
|
||||
Name: "block",
|
||||
Usage: "goroutine blocking profile",
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
output, err := httpGetRequest(client, "/debug/pprof/block")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var pprofThreadcreateCommand = cli.Command{
|
||||
Name: "threadcreate",
|
||||
Usage: "goroutine thread creating profile",
|
||||
Action: func(context *cli.Context) error {
|
||||
client := getPProfClient(context)
|
||||
|
||||
output, err := httpGetRequest(client, "/debug/pprof/threadcreate")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(os.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
func getPProfClient(context *cli.Context) *http.Client {
|
||||
dialer := getPProfDialer(context.GlobalString("debug-socket"))
|
||||
|
||||
tr := &http.Transport{
|
||||
Dial: dialer.pprofDial,
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
return client
|
||||
}
|
||||
|
||||
func httpGetRequest(client *http.Client, request string) (io.ReadCloser, error) {
|
||||
resp, err := client.Get("http://." + request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.Errorf("http get failed with status: %s", resp.Status)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
29
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof_unix.go
generated
vendored
Normal file
29
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof_unix.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// +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 pprof
|
||||
|
||||
import "net"
|
||||
|
||||
func (d *pprofDialer) pprofDial(proto, addr string) (conn net.Conn, err error) {
|
||||
return net.Dial(d.proto, d.addr)
|
||||
}
|
||||
|
||||
func getPProfDialer(addr string) *pprofDialer {
|
||||
return &pprofDialer{"unix", addr}
|
||||
}
|
||||
31
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof_windows.go
generated
vendored
Normal file
31
vendor/github.com/containerd/containerd/cmd/ctr/commands/pprof/pprof_windows.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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 pprof
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
winio "github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
func (d *pprofDialer) pprofDial(proto, addr string) (conn net.Conn, err error) {
|
||||
return winio.DialPipe(d.addr, nil)
|
||||
}
|
||||
|
||||
func getPProfDialer(addr string) *pprofDialer {
|
||||
return &pprofDialer{"winpipe", addr}
|
||||
}
|
||||
108
vendor/github.com/containerd/containerd/cmd/ctr/commands/resolver.go
generated
vendored
Normal file
108
vendor/github.com/containerd/containerd/cmd/ctr/commands/resolver.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
gocontext "context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// PushTracker returns a new InMemoryTracker which tracks the ref status
|
||||
var PushTracker = docker.NewInMemoryTracker()
|
||||
|
||||
func passwordPrompt() (string, error) {
|
||||
c := console.Current()
|
||||
defer c.Reset()
|
||||
|
||||
if err := c.DisableEcho(); err != nil {
|
||||
return "", errors.Wrap(err, "failed to disable echo")
|
||||
}
|
||||
|
||||
line, _, err := bufio.NewReader(c).ReadLine()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to read line")
|
||||
}
|
||||
return string(line), nil
|
||||
}
|
||||
|
||||
// GetResolver prepares the resolver from the environment and options
|
||||
func GetResolver(ctx gocontext.Context, clicontext *cli.Context) (remotes.Resolver, error) {
|
||||
username := clicontext.String("user")
|
||||
var secret string
|
||||
if i := strings.IndexByte(username, ':'); i > 0 {
|
||||
secret = username[i+1:]
|
||||
username = username[0:i]
|
||||
}
|
||||
options := docker.ResolverOptions{
|
||||
PlainHTTP: clicontext.Bool("plain-http"),
|
||||
Tracker: PushTracker,
|
||||
}
|
||||
if username != "" {
|
||||
if secret == "" {
|
||||
fmt.Printf("Password: ")
|
||||
|
||||
var err error
|
||||
secret, err = passwordPrompt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Print("\n")
|
||||
}
|
||||
} else if rt := clicontext.String("refresh"); rt != "" {
|
||||
secret = rt
|
||||
}
|
||||
|
||||
options.Credentials = func(host string) (string, string, error) {
|
||||
// Only one host
|
||||
return username, secret, nil
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: clicontext.Bool("insecure"),
|
||||
},
|
||||
ExpectContinueTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
options.Client = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
return docker.NewResolver(options), nil
|
||||
}
|
||||
258
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run.go
generated
vendored
Normal file
258
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
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 run
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// ContainerFlags are cli flags specifying container options
|
||||
var ContainerFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config,c",
|
||||
Usage: "path to the runtime-specific spec config file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "checkpoint",
|
||||
Usage: "provide the checkpoint digest to restore the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "specify the working directory of the process",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env",
|
||||
Usage: "specify additional container environment variables (i.e. FOO=bar)",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "label",
|
||||
Usage: "specify additional labels (i.e. foo=bar)",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "mount",
|
||||
Usage: "specify additional container mount (ex: type=bind,src=/tmp,dest=/host,options=rbind:ro)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "net-host",
|
||||
Usage: "enable host networking for the container",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "read-only",
|
||||
Usage: "set the containers filesystem as readonly",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "runtime",
|
||||
Usage: "runtime name (io.containerd.runtime.v1.linux, io.containerd.runtime.v1.windows, io.containerd.runtime.v1.com.vmware.linux)",
|
||||
Value: fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS),
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty,t",
|
||||
Usage: "allocate a TTY for the container",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "with-ns",
|
||||
Usage: "specify existing Linux namespaces to join at container runtime (format '<nstype>:<path>')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Usage: "file path to write the task's pid",
|
||||
},
|
||||
}
|
||||
|
||||
func loadSpec(path string, s *specs.Spec) error {
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.New("cannot load spec config file")
|
||||
}
|
||||
if err := json.Unmarshal(raw, s); err != nil {
|
||||
return errors.Errorf("decoding spec config file failed, current supported OCI runtime-spec : v%s", specs.Version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func withMounts(context *cli.Context) oci.SpecOpts {
|
||||
return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *specs.Spec) error {
|
||||
mounts := make([]specs.Mount, 0)
|
||||
for _, mount := range context.StringSlice("mount") {
|
||||
m, err := parseMountFlag(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mounts = append(mounts, m)
|
||||
}
|
||||
return oci.WithMounts(mounts)(ctx, client, container, s)
|
||||
}
|
||||
}
|
||||
|
||||
// parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw"
|
||||
func parseMountFlag(m string) (specs.Mount, error) {
|
||||
mount := specs.Mount{}
|
||||
r := csv.NewReader(strings.NewReader(m))
|
||||
|
||||
fields, err := r.Read()
|
||||
if err != nil {
|
||||
return mount, err
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
v := strings.Split(field, "=")
|
||||
if len(v) != 2 {
|
||||
return mount, fmt.Errorf("invalid mount specification: expected key=val")
|
||||
}
|
||||
|
||||
key := v[0]
|
||||
val := v[1]
|
||||
switch key {
|
||||
case "type":
|
||||
mount.Type = val
|
||||
case "source", "src":
|
||||
mount.Source = val
|
||||
case "destination", "dst":
|
||||
mount.Destination = val
|
||||
case "options":
|
||||
mount.Options = strings.Split(val, ":")
|
||||
default:
|
||||
return mount, fmt.Errorf("mount option %q not supported", key)
|
||||
}
|
||||
}
|
||||
|
||||
return mount, nil
|
||||
}
|
||||
|
||||
// Command runs a container
|
||||
var Command = cli.Command{
|
||||
Name: "run",
|
||||
Usage: "run a container",
|
||||
ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
|
||||
Flags: append([]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "rm",
|
||||
Usage: "remove the container after running",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "null-io",
|
||||
Usage: "send all IO to /dev/null",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach,d",
|
||||
Usage: "detach from the task after it has started execution",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "fifo-dir",
|
||||
Usage: "directory used for storing IO FIFOs",
|
||||
},
|
||||
}, append(commands.SnapshotterFlags, ContainerFlags...)...),
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
err error
|
||||
|
||||
id = context.Args().Get(1)
|
||||
ref = context.Args().First()
|
||||
tty = context.Bool("tty")
|
||||
detach = context.Bool("detach")
|
||||
)
|
||||
|
||||
if ref == "" {
|
||||
return errors.New("image ref must be provided")
|
||||
}
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := NewContainer(ctx, client, context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if context.Bool("rm") && !detach {
|
||||
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
}
|
||||
opts := getNewTaskOpts(context)
|
||||
ioOpts := []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))}
|
||||
task, err := tasks.NewTask(ctx, client, container, context.String("checkpoint"), tty, context.Bool("null-io"), ioOpts, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var statusC <-chan containerd.ExitStatus
|
||||
if !detach {
|
||||
defer task.Delete(ctx)
|
||||
if statusC, err = task.Wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if context.IsSet("pid-file") {
|
||||
if err := commands.WritePidFile(context.String("pid-file"), int(task.Pid())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var con console.Console
|
||||
if tty {
|
||||
con = console.Current()
|
||||
defer con.Reset()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := task.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if detach {
|
||||
return nil
|
||||
}
|
||||
if tty {
|
||||
if err := tasks.HandleConsoleResize(ctx, task, con); err != nil {
|
||||
logrus.WithError(err).Error("console resize")
|
||||
}
|
||||
} else {
|
||||
sigc := commands.ForwardAllSignals(ctx, task)
|
||||
defer commands.StopCatch(sigc)
|
||||
}
|
||||
status := <-statusC
|
||||
code, _, err := status.Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return cli.NewExitError("", int(code))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
161
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run_unix.go
generated
vendored
Normal file
161
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run_unix.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// +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 run
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ContainerFlags = append(ContainerFlags, cli.BoolFlag{
|
||||
Name: "rootfs",
|
||||
Usage: "use custom rootfs that is not managed by containerd snapshotter",
|
||||
}, cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "disable use of pivot-root (linux only)",
|
||||
})
|
||||
}
|
||||
|
||||
// NewContainer creates a new container
|
||||
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
id = context.Args().Get(1)
|
||||
args = context.Args()[2:]
|
||||
)
|
||||
|
||||
if raw := context.String("checkpoint"); raw != "" {
|
||||
im, err := client.GetImage(ctx, raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.NewContainer(ctx, id, containerd.WithCheckpoint(im, id))
|
||||
}
|
||||
|
||||
var (
|
||||
opts []oci.SpecOpts
|
||||
cOpts []containerd.NewContainerOpts
|
||||
spec containerd.NewContainerOpts
|
||||
)
|
||||
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
||||
opts = append(opts, withMounts(context))
|
||||
cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
|
||||
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
|
||||
if context.Bool("rootfs") {
|
||||
opts = append(opts, oci.WithRootFSPath(ref))
|
||||
} else {
|
||||
snapshotter := context.String("snapshotter")
|
||||
image, err := client.GetImage(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unpacked, err := image.IsUnpacked(ctx, snapshotter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !unpacked {
|
||||
if err := image.Unpack(ctx, snapshotter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
opts = append(opts, oci.WithImageConfig(image))
|
||||
cOpts = append(cOpts,
|
||||
containerd.WithImage(image),
|
||||
containerd.WithSnapshotter(snapshotter),
|
||||
// Even when "readonly" is set, we don't use KindView snapshot here. (#1495)
|
||||
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
|
||||
// after creating some mount points on demand.
|
||||
containerd.WithNewSnapshot(id, image))
|
||||
}
|
||||
if context.Bool("readonly") {
|
||||
opts = append(opts, oci.WithRootFSReadonly())
|
||||
}
|
||||
if len(args) > 0 {
|
||||
opts = append(opts, oci.WithProcessArgs(args...))
|
||||
}
|
||||
if cwd := context.String("cwd"); cwd != "" {
|
||||
opts = append(opts, oci.WithProcessCwd(cwd))
|
||||
}
|
||||
if context.Bool("tty") {
|
||||
opts = append(opts, oci.WithTTY)
|
||||
}
|
||||
if context.Bool("net-host") {
|
||||
opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf)
|
||||
}
|
||||
joinNs := context.StringSlice("with-ns")
|
||||
for _, ns := range joinNs {
|
||||
parts := strings.Split(ns, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("joining a Linux namespace using --with-ns requires the format 'nstype:path'")
|
||||
}
|
||||
if !validNamespace(parts[0]) {
|
||||
return nil, errors.New("the Linux namespace type specified in --with-ns is not valid: " + parts[0])
|
||||
}
|
||||
opts = append(opts, oci.WithLinuxNamespace(specs.LinuxNamespace{
|
||||
Type: specs.LinuxNamespaceType(parts[0]),
|
||||
Path: parts[1],
|
||||
}))
|
||||
}
|
||||
if context.IsSet("config") {
|
||||
var s specs.Spec
|
||||
if err := loadSpec(context.String("config"), &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec = containerd.WithSpec(&s, opts...)
|
||||
} else {
|
||||
spec = containerd.WithNewSpec(opts...)
|
||||
}
|
||||
cOpts = append(cOpts, spec)
|
||||
|
||||
// oci.WithImageConfig (WithUsername, WithUserID) depends on rootfs snapshot for resolving /etc/passwd.
|
||||
// So cOpts needs to have precedence over opts.
|
||||
// TODO: WithUsername, WithUserID should additionally support non-snapshot rootfs
|
||||
return client.NewContainer(ctx, id, cOpts...)
|
||||
}
|
||||
|
||||
func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts {
|
||||
if context.Bool("no-pivot") {
|
||||
return []containerd.NewTaskOpts{containerd.WithNoPivotRoot}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validNamespace(ns string) bool {
|
||||
linuxNs := specs.LinuxNamespaceType(ns)
|
||||
switch linuxNs {
|
||||
case specs.PIDNamespace,
|
||||
specs.NetworkNamespace,
|
||||
specs.UTSNamespace,
|
||||
specs.MountNamespace,
|
||||
specs.UserNamespace,
|
||||
specs.IPCNamespace,
|
||||
specs.CgroupNamespace:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
99
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run_windows.go
generated
vendored
Normal file
99
vendor/github.com/containerd/containerd/cmd/ctr/commands/run/run_windows.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 run
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func withTTY(terminal bool) oci.SpecOpts {
|
||||
if !terminal {
|
||||
return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error {
|
||||
s.Process.Terminal = false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
con := console.Current()
|
||||
size, err := con.Size()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("console size")
|
||||
}
|
||||
return oci.WithTTY(int(size.Width), int(size.Height))
|
||||
}
|
||||
|
||||
// NewContainer creates a new container
|
||||
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
id = context.Args().Get(1)
|
||||
args = context.Args()[2:]
|
||||
)
|
||||
|
||||
image, err := client.GetImage(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
opts []oci.SpecOpts
|
||||
cOpts []containerd.NewContainerOpts
|
||||
spec containerd.NewContainerOpts
|
||||
)
|
||||
opts = append(opts, oci.WithImageConfig(image))
|
||||
opts = append(opts, oci.WithEnv(context.StringSlice("env")))
|
||||
opts = append(opts, withMounts(context))
|
||||
opts = append(opts, withTTY(context.Bool("tty")))
|
||||
if len(args) > 0 {
|
||||
opts = append(opts, oci.WithProcessArgs(args...))
|
||||
}
|
||||
if cwd := context.String("cwd"); cwd != "" {
|
||||
opts = append(opts, oci.WithProcessCwd(cwd))
|
||||
}
|
||||
|
||||
if context.IsSet("config") {
|
||||
var s specs.Spec
|
||||
if err := loadSpec(context.String("config"), &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec = containerd.WithSpec(&s, opts...)
|
||||
} else {
|
||||
spec = containerd.WithNewSpec(opts...)
|
||||
}
|
||||
|
||||
cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
|
||||
cOpts = append(cOpts, containerd.WithImage(image))
|
||||
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
||||
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
|
||||
cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil))
|
||||
cOpts = append(cOpts, spec)
|
||||
|
||||
return client.NewContainer(ctx, id, cOpts...)
|
||||
}
|
||||
|
||||
func getNewTaskOpts(_ *cli.Context) []containerd.NewTaskOpts {
|
||||
return nil
|
||||
}
|
||||
93
vendor/github.com/containerd/containerd/cmd/ctr/commands/shim/io_unix.go
generated
vendored
Normal file
93
vendor/github.com/containerd/containerd/cmd/ctr/commands/shim/io_unix.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// +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 shim
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/fifo"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buffer := make([]byte, 32<<10)
|
||||
return &buffer
|
||||
},
|
||||
}
|
||||
|
||||
func prepareStdio(stdin, stdout, stderr string, console bool) (wg *sync.WaitGroup, err error) {
|
||||
wg = &sync.WaitGroup{}
|
||||
ctx := gocontext.Background()
|
||||
|
||||
f, err := fifo.OpenFifo(ctx, stdin, unix.O_WRONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(c io.Closer) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}(f)
|
||||
go func(w io.WriteCloser) {
|
||||
p := bufPool.Get().(*[]byte)
|
||||
defer bufPool.Put(p)
|
||||
io.CopyBuffer(w, os.Stdin, *p)
|
||||
w.Close()
|
||||
}(f)
|
||||
|
||||
f, err = fifo.OpenFifo(ctx, stdout, unix.O_RDONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(c io.Closer) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}(f)
|
||||
wg.Add(1)
|
||||
go func(r io.ReadCloser) {
|
||||
io.Copy(os.Stdout, r)
|
||||
r.Close()
|
||||
wg.Done()
|
||||
}(f)
|
||||
|
||||
f, err = fifo.OpenFifo(ctx, stderr, unix.O_RDONLY|unix.O_CREAT|unix.O_NONBLOCK, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(c io.Closer) {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}(f)
|
||||
if !console {
|
||||
wg.Add(1)
|
||||
go func(r io.ReadCloser) {
|
||||
io.Copy(os.Stderr, r)
|
||||
r.Close()
|
||||
wg.Done()
|
||||
}(f)
|
||||
}
|
||||
|
||||
return wg, nil
|
||||
}
|
||||
246
vendor/github.com/containerd/containerd/cmd/ctr/commands/shim/shim.go
generated
vendored
Normal file
246
vendor/github.com/containerd/containerd/cmd/ctr/commands/shim/shim.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// +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 shim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
gocontext "context"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
shim "github.com/containerd/containerd/linux/shim/v1"
|
||||
"github.com/containerd/typeurl"
|
||||
ptypes "github.com/gogo/protobuf/types"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stevvooe/ttrpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var empty = &ptypes.Empty{}
|
||||
|
||||
var fifoFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "stdin",
|
||||
Usage: "specify the path to the stdin fifo",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stdout",
|
||||
Usage: "specify the path to the stdout fifo",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stderr",
|
||||
Usage: "specify the path to the stderr fifo",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty,t",
|
||||
Usage: "enable tty support",
|
||||
},
|
||||
}
|
||||
|
||||
// Command is the cli command for interacting with a shim
|
||||
var Command = cli.Command{
|
||||
Name: "shim",
|
||||
Usage: "interact with a shim directly",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "socket",
|
||||
Usage: "socket on which to connect to the shim",
|
||||
},
|
||||
},
|
||||
Subcommands: []cli.Command{
|
||||
deleteCommand,
|
||||
execCommand,
|
||||
startCommand,
|
||||
stateCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var startCommand = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start a container with a shim",
|
||||
Action: func(context *cli.Context) error {
|
||||
service, err := getShimService(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = service.Start(gocontext.Background(), &shim.StartRequest{
|
||||
ID: context.Args().First(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "delete a container with a shim",
|
||||
Action: func(context *cli.Context) error {
|
||||
service, err := getShimService(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := service.Delete(gocontext.Background(), empty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("container deleted and returned exit status %d\n", r.ExitStatus)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var stateCommand = cli.Command{
|
||||
Name: "state",
|
||||
Usage: "get the state of all the processes of the shim",
|
||||
Action: func(context *cli.Context) error {
|
||||
service, err := getShimService(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := service.State(gocontext.Background(), &shim.StateRequest{
|
||||
ID: context.Args().First(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commands.PrintAsJSON(r)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var execCommand = cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "exec a new process in the shim's container",
|
||||
Flags: append(fifoFlags,
|
||||
cli.BoolFlag{
|
||||
Name: "attach,a",
|
||||
Usage: "stay attached to the container and open the fifos",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env,e",
|
||||
Usage: "add environment vars",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "current working directory",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "spec",
|
||||
Usage: "runtime spec",
|
||||
},
|
||||
),
|
||||
Action: func(context *cli.Context) error {
|
||||
service, err := getShimService(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
id = context.Args().First()
|
||||
ctx = gocontext.Background()
|
||||
)
|
||||
|
||||
if id == "" {
|
||||
return errors.New("exec id must be provided")
|
||||
}
|
||||
|
||||
tty := context.Bool("tty")
|
||||
wg, err := prepareStdio(context.String("stdin"), context.String("stdout"), context.String("stderr"), tty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read spec file and extract Any object
|
||||
spec, err := ioutil.ReadFile(context.String("spec"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url, err := typeurl.TypeURL(specs.Process{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rq := &shim.ExecProcessRequest{
|
||||
ID: id,
|
||||
Spec: &ptypes.Any{
|
||||
TypeUrl: url,
|
||||
Value: spec,
|
||||
},
|
||||
Stdin: context.String("stdin"),
|
||||
Stdout: context.String("stdout"),
|
||||
Stderr: context.String("stderr"),
|
||||
Terminal: tty,
|
||||
}
|
||||
if _, err := service.Exec(ctx, rq); err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := service.Start(ctx, &shim.StartRequest{
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("exec running with pid %d\n", r.Pid)
|
||||
if context.Bool("attach") {
|
||||
logrus.Info("attaching")
|
||||
if tty {
|
||||
current := console.Current()
|
||||
defer current.Reset()
|
||||
if err := current.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
size, err := current.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := service.ResizePty(ctx, &shim.ResizePtyRequest{
|
||||
ID: id,
|
||||
Width: uint32(size.Width),
|
||||
Height: uint32(size.Height),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func getShimService(context *cli.Context) (shim.ShimService, error) {
|
||||
bindSocket := context.GlobalString("socket")
|
||||
if bindSocket == "" {
|
||||
return nil, errors.New("socket path must be specified")
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", "\x00"+bindSocket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := ttrpc.NewClient(conn)
|
||||
|
||||
// TODO(stevvooe): This actually leaks the connection. We were leaking it
|
||||
// before, so may not be a huge deal.
|
||||
|
||||
return shim.NewShimClient(client), nil
|
||||
}
|
||||
60
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_linux.go
generated
vendored
Normal file
60
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_linux.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var signalMap = map[string]syscall.Signal{
|
||||
"ABRT": unix.SIGABRT,
|
||||
"ALRM": unix.SIGALRM,
|
||||
"BUS": unix.SIGBUS,
|
||||
"CHLD": unix.SIGCHLD,
|
||||
"CLD": unix.SIGCLD,
|
||||
"CONT": unix.SIGCONT,
|
||||
"FPE": unix.SIGFPE,
|
||||
"HUP": unix.SIGHUP,
|
||||
"ILL": unix.SIGILL,
|
||||
"INT": unix.SIGINT,
|
||||
"IO": unix.SIGIO,
|
||||
"IOT": unix.SIGIOT,
|
||||
"KILL": unix.SIGKILL,
|
||||
"PIPE": unix.SIGPIPE,
|
||||
"POLL": unix.SIGPOLL,
|
||||
"PROF": unix.SIGPROF,
|
||||
"PWR": unix.SIGPWR,
|
||||
"QUIT": unix.SIGQUIT,
|
||||
"SEGV": unix.SIGSEGV,
|
||||
"STKFLT": unix.SIGSTKFLT,
|
||||
"STOP": unix.SIGSTOP,
|
||||
"SYS": unix.SIGSYS,
|
||||
"TERM": unix.SIGTERM,
|
||||
"TRAP": unix.SIGTRAP,
|
||||
"TSTP": unix.SIGTSTP,
|
||||
"TTIN": unix.SIGTTIN,
|
||||
"TTOU": unix.SIGTTOU,
|
||||
"URG": unix.SIGURG,
|
||||
"USR1": unix.SIGUSR1,
|
||||
"USR2": unix.SIGUSR2,
|
||||
"VTALRM": unix.SIGVTALRM,
|
||||
"WINCH": unix.SIGWINCH,
|
||||
"XCPU": unix.SIGXCPU,
|
||||
"XFSZ": unix.SIGXFSZ,
|
||||
}
|
||||
58
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_unix.go
generated
vendored
Normal file
58
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_unix.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// +build darwin freebsd solaris
|
||||
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var signalMap = map[string]syscall.Signal{
|
||||
"ABRT": unix.SIGABRT,
|
||||
"ALRM": unix.SIGALRM,
|
||||
"BUS": unix.SIGBUS,
|
||||
"CHLD": unix.SIGCHLD,
|
||||
"CONT": unix.SIGCONT,
|
||||
"FPE": unix.SIGFPE,
|
||||
"HUP": unix.SIGHUP,
|
||||
"ILL": unix.SIGILL,
|
||||
"INT": unix.SIGINT,
|
||||
"IO": unix.SIGIO,
|
||||
"IOT": unix.SIGIOT,
|
||||
"KILL": unix.SIGKILL,
|
||||
"PIPE": unix.SIGPIPE,
|
||||
"PROF": unix.SIGPROF,
|
||||
"QUIT": unix.SIGQUIT,
|
||||
"SEGV": unix.SIGSEGV,
|
||||
"STOP": unix.SIGSTOP,
|
||||
"SYS": unix.SIGSYS,
|
||||
"TERM": unix.SIGTERM,
|
||||
"TRAP": unix.SIGTRAP,
|
||||
"TSTP": unix.SIGTSTP,
|
||||
"TTIN": unix.SIGTTIN,
|
||||
"TTOU": unix.SIGTTOU,
|
||||
"URG": unix.SIGURG,
|
||||
"USR1": unix.SIGUSR1,
|
||||
"USR2": unix.SIGUSR2,
|
||||
"VTALRM": unix.SIGVTALRM,
|
||||
"WINCH": unix.SIGWINCH,
|
||||
"XCPU": unix.SIGXCPU,
|
||||
"XFSZ": unix.SIGXFSZ,
|
||||
}
|
||||
39
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_windows.go
generated
vendored
Normal file
39
vendor/github.com/containerd/containerd/cmd/ctr/commands/signal_map_windows.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var signalMap = map[string]syscall.Signal{
|
||||
"HUP": syscall.Signal(windows.SIGHUP),
|
||||
"INT": syscall.Signal(windows.SIGINT),
|
||||
"QUIT": syscall.Signal(windows.SIGQUIT),
|
||||
"SIGILL": syscall.Signal(windows.SIGILL),
|
||||
"TRAP": syscall.Signal(windows.SIGTRAP),
|
||||
"ABRT": syscall.Signal(windows.SIGABRT),
|
||||
"BUS": syscall.Signal(windows.SIGBUS),
|
||||
"FPE": syscall.Signal(windows.SIGFPE),
|
||||
"KILL": syscall.Signal(windows.SIGKILL),
|
||||
"SEGV": syscall.Signal(windows.SIGSEGV),
|
||||
"PIPE": syscall.Signal(windows.SIGPIPE),
|
||||
"ALRM": syscall.Signal(windows.SIGALRM),
|
||||
"TERM": syscall.Signal(windows.SIGTERM),
|
||||
}
|
||||
75
vendor/github.com/containerd/containerd/cmd/ctr/commands/signals.go
generated
vendored
Normal file
75
vendor/github.com/containerd/containerd/cmd/ctr/commands/signals.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 commands
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type killer interface {
|
||||
Kill(gocontext.Context, syscall.Signal, ...containerd.KillOpts) error
|
||||
}
|
||||
|
||||
// ForwardAllSignals forwards signals
|
||||
func ForwardAllSignals(ctx gocontext.Context, task killer) chan os.Signal {
|
||||
sigc := make(chan os.Signal, 128)
|
||||
signal.Notify(sigc)
|
||||
go func() {
|
||||
for s := range sigc {
|
||||
logrus.Debug("forwarding signal ", s)
|
||||
if err := task.Kill(ctx, s.(syscall.Signal)); err != nil {
|
||||
logrus.WithError(err).Errorf("forward signal %s", s)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sigc
|
||||
}
|
||||
|
||||
// StopCatch stops and closes a channel
|
||||
func StopCatch(sigc chan os.Signal) {
|
||||
signal.Stop(sigc)
|
||||
close(sigc)
|
||||
}
|
||||
|
||||
// ParseSignal parses a given string into a syscall.Signal
|
||||
// it checks that the signal exists in the platform-appropriate signalMap
|
||||
func ParseSignal(rawSignal string) (syscall.Signal, error) {
|
||||
s, err := strconv.Atoi(rawSignal)
|
||||
if err == nil {
|
||||
sig := syscall.Signal(s)
|
||||
for _, msig := range signalMap {
|
||||
if sig == msig {
|
||||
return sig, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||
}
|
||||
signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("unknown signal %q", rawSignal)
|
||||
}
|
||||
return signal, nil
|
||||
}
|
||||
629
vendor/github.com/containerd/containerd/cmd/ctr/commands/snapshots/snapshots.go
generated
vendored
Normal file
629
vendor/github.com/containerd/containerd/cmd/ctr/commands/snapshots/snapshots.go
generated
vendored
Normal file
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
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 snapshots
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/diff"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/progress"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is the cli command for managing snapshots
|
||||
var Command = cli.Command{
|
||||
Name: "snapshots",
|
||||
Aliases: []string{"snapshot"},
|
||||
Usage: "manage snapshots",
|
||||
Flags: commands.SnapshotterFlags,
|
||||
Subcommands: cli.Commands{
|
||||
commitCommand,
|
||||
diffCommand,
|
||||
infoCommand,
|
||||
listCommand,
|
||||
mountCommand,
|
||||
prepareCommand,
|
||||
removeCommand,
|
||||
setLabelCommand,
|
||||
treeCommand,
|
||||
unpackCommand,
|
||||
usageCommand,
|
||||
viewCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list snapshots",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
snapshotter = client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
)
|
||||
fmt.Fprintln(tw, "KEY\tPARENT\tKIND\t")
|
||||
if err := snapshotter.Walk(ctx, func(ctx gocontext.Context, info snapshots.Info) error {
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t\n",
|
||||
info.Name,
|
||||
info.Parent,
|
||||
info.Kind)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var diffCommand = cli.Command{
|
||||
Name: "diff",
|
||||
Usage: "get the diff of two snapshots. the default second snapshot is the first snapshot's parent.",
|
||||
ArgsUsage: "[flags] <idA> [<idB>]",
|
||||
Flags: append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "media-type",
|
||||
Usage: "media type to use for creating diff",
|
||||
Value: ocispec.MediaTypeImageLayerGzip,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ref",
|
||||
Usage: "content upload reference to use",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "keep",
|
||||
Usage: "keep diff content. up to creator to delete it.",
|
||||
},
|
||||
}, commands.LabelFlag),
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
idA = context.Args().First()
|
||||
idB = context.Args().Get(1)
|
||||
)
|
||||
if idA == "" {
|
||||
return errors.New("snapshot id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
ctx, done, err := client.WithLease(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer done()
|
||||
|
||||
var desc ocispec.Descriptor
|
||||
labels := commands.LabelArgs(context.StringSlice("label"))
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
|
||||
fmt.Println(context.String("media-type"))
|
||||
|
||||
if context.Bool("keep") {
|
||||
labels["containerd.io/gc.root"] = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
opts := []diff.Opt{
|
||||
diff.WithMediaType(context.String("media-type")),
|
||||
diff.WithReference(context.String("ref")),
|
||||
diff.WithLabels(labels),
|
||||
}
|
||||
|
||||
if idB == "" {
|
||||
desc, err = rootfs.CreateDiff(ctx, idA, snapshotter, client.DiffService(), opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var a, b []mount.Mount
|
||||
ds := client.DiffService()
|
||||
|
||||
a, err = getMounts(ctx, idA, snapshotter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err = getMounts(ctx, idB, snapshotter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desc, err = ds.Compare(ctx, a, b, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ra, err := client.ContentStore().ReaderAt(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(os.Stdout, content.NewReader(ra))
|
||||
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
func getMounts(ctx gocontext.Context, id string, sn snapshots.Snapshotter) ([]mount.Mount, error) {
|
||||
var mounts []mount.Mount
|
||||
info, err := sn.Stat(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.Kind == snapshots.KindActive {
|
||||
mounts, err = sn.Mounts(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
key := fmt.Sprintf("%s-view-key", id)
|
||||
mounts, err = sn.View(ctx, key, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sn.Remove(ctx, key)
|
||||
}
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
var usageCommand = cli.Command{
|
||||
Name: "usage",
|
||||
Usage: "usage snapshots",
|
||||
ArgsUsage: "[flags] [<key>, ...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "b",
|
||||
Usage: "display size in bytes",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var displaySize func(int64) string
|
||||
if context.Bool("b") {
|
||||
displaySize = func(s int64) string {
|
||||
return fmt.Sprintf("%d", s)
|
||||
}
|
||||
} else {
|
||||
displaySize = func(s int64) string {
|
||||
return progress.Bytes(s).String()
|
||||
}
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
snapshotter = client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
)
|
||||
fmt.Fprintln(tw, "KEY\tSIZE\tINODES\t")
|
||||
if context.NArg() == 0 {
|
||||
if err := snapshotter.Walk(ctx, func(ctx gocontext.Context, info snapshots.Info) error {
|
||||
usage, err := snapshotter.Usage(ctx, info.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(tw, "%v\t%s\t%d\t\n", info.Name, displaySize(usage.Size), usage.Inodes)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for _, id := range context.Args() {
|
||||
usage, err := snapshotter.Usage(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(tw, "%v\t%s\t%d\t\n", id, displaySize(usage.Size), usage.Inodes)
|
||||
}
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var removeCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
ArgsUsage: "<key> [<key>, ...]",
|
||||
Usage: "remove snapshots",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
for _, key := range context.Args() {
|
||||
err = snapshotter.Remove(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to remove %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var prepareCommand = cli.Command{
|
||||
Name: "prepare",
|
||||
Usage: "prepare a snapshot from a committed snapshot",
|
||||
ArgsUsage: "[flags] <key> [<parent>]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "target, t",
|
||||
Usage: "mount target path, will print mount, if provided",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if narg := context.NArg(); narg < 1 || narg > 2 {
|
||||
return cli.ShowSubcommandHelp(context)
|
||||
}
|
||||
var (
|
||||
target = context.String("target")
|
||||
key = context.Args().Get(0)
|
||||
parent = context.Args().Get(1)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
mounts, err := snapshotter.Prepare(ctx, key, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if target != "" {
|
||||
printMounts(target, mounts)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var viewCommand = cli.Command{
|
||||
Name: "view",
|
||||
Usage: "create a read-only snapshot from a committed snapshot",
|
||||
ArgsUsage: "[flags] <key> [<parent>]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "target, t",
|
||||
Usage: "mount target path, will print mount, if provided",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
if narg := context.NArg(); narg < 1 || narg > 2 {
|
||||
return cli.ShowSubcommandHelp(context)
|
||||
}
|
||||
var (
|
||||
target = context.String("target")
|
||||
key = context.Args().Get(0)
|
||||
parent = context.Args().Get(1)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
mounts, err := snapshotter.View(ctx, key, parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if target != "" {
|
||||
printMounts(target, mounts)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var mountCommand = cli.Command{
|
||||
Name: "mounts",
|
||||
Aliases: []string{"m", "mount"},
|
||||
Usage: "mount gets mount commands for the snapshots",
|
||||
ArgsUsage: "<target> <key>",
|
||||
Action: func(context *cli.Context) error {
|
||||
if context.NArg() != 2 {
|
||||
return cli.ShowSubcommandHelp(context)
|
||||
}
|
||||
var (
|
||||
target = context.Args().Get(0)
|
||||
key = context.Args().Get(1)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
mounts, err := snapshotter.Mounts(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printMounts(target, mounts)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var commitCommand = cli.Command{
|
||||
Name: "commit",
|
||||
Usage: "commit an active snapshot into the provided name",
|
||||
ArgsUsage: "<key> <active>",
|
||||
Action: func(context *cli.Context) error {
|
||||
if context.NArg() != 2 {
|
||||
return cli.ShowSubcommandHelp(context)
|
||||
}
|
||||
var (
|
||||
key = context.Args().Get(0)
|
||||
active = context.Args().Get(1)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
return snapshotter.Commit(ctx, key, active)
|
||||
},
|
||||
}
|
||||
|
||||
var treeCommand = cli.Command{
|
||||
Name: "tree",
|
||||
Usage: "display tree view of snapshot branches",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
snapshotter = client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
tree = newSnapshotTree()
|
||||
)
|
||||
|
||||
if err := snapshotter.Walk(ctx, func(ctx gocontext.Context, info snapshots.Info) error {
|
||||
// Get or create node and add node details
|
||||
tree.add(info)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printTree(tree)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var infoCommand = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "get info about a snapshot",
|
||||
ArgsUsage: "<key>",
|
||||
Action: func(context *cli.Context) error {
|
||||
if context.NArg() != 1 {
|
||||
return cli.ShowSubcommandHelp(context)
|
||||
}
|
||||
|
||||
key := context.Args().Get(0)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
info, err := snapshotter.Stat(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commands.PrintAsJSON(info)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var setLabelCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "add labels to content",
|
||||
ArgsUsage: "<name> [<label>=<value> ...]",
|
||||
Description: "labels snapshots in the snapshotter",
|
||||
Action: func(context *cli.Context) error {
|
||||
key, labels := commands.ObjectWithLabelArgs(context)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
snapshotter := client.SnapshotService(context.GlobalString("snapshotter"))
|
||||
|
||||
info := snapshots.Info{
|
||||
Name: key,
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for k, v := range labels {
|
||||
paths = append(paths, fmt.Sprintf("labels.%s", k))
|
||||
if v != "" {
|
||||
info.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing updated, do no clear
|
||||
if len(paths) == 0 {
|
||||
info, err = snapshotter.Stat(ctx, info.Name)
|
||||
} else {
|
||||
info, err = snapshotter.Update(ctx, info, paths...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range info.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var unpackCommand = cli.Command{
|
||||
Name: "unpack",
|
||||
Usage: "unpack applies layers from a manifest to a snapshot",
|
||||
ArgsUsage: "[flags] <digest>",
|
||||
Flags: commands.SnapshotterFlags,
|
||||
Action: func(context *cli.Context) error {
|
||||
dgst, err := digest.Parse(context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
log.G(ctx).Debugf("unpacking layers from manifest %s", dgst.String())
|
||||
// TODO: Support unpack by name
|
||||
images, err := client.ListImages(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var unpacked bool
|
||||
for _, image := range images {
|
||||
if image.Target().Digest == dgst {
|
||||
fmt.Printf("unpacking %s (%s)...", dgst, image.Target().MediaType)
|
||||
if err := image.Unpack(ctx, context.String("snapshotter")); err != nil {
|
||||
fmt.Println()
|
||||
return err
|
||||
}
|
||||
fmt.Println("done")
|
||||
unpacked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !unpacked {
|
||||
return errors.New("manifest not found")
|
||||
}
|
||||
// TODO: Get rootfs from Image
|
||||
//log.G(ctx).Infof("chain ID: %s", chainID.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
type snapshotTree struct {
|
||||
nodes []*snapshotTreeNode
|
||||
index map[string]*snapshotTreeNode
|
||||
}
|
||||
|
||||
func newSnapshotTree() *snapshotTree {
|
||||
return &snapshotTree{
|
||||
index: make(map[string]*snapshotTreeNode),
|
||||
}
|
||||
}
|
||||
|
||||
type snapshotTreeNode struct {
|
||||
info snapshots.Info
|
||||
children []string
|
||||
}
|
||||
|
||||
func (st *snapshotTree) add(info snapshots.Info) *snapshotTreeNode {
|
||||
entry, ok := st.index[info.Name]
|
||||
if !ok {
|
||||
entry = &snapshotTreeNode{info: info}
|
||||
st.nodes = append(st.nodes, entry)
|
||||
st.index[info.Name] = entry
|
||||
} else {
|
||||
entry.info = info // update info if we created placeholder
|
||||
}
|
||||
|
||||
if info.Parent != "" {
|
||||
pn := st.get(info.Parent)
|
||||
if pn == nil {
|
||||
// create a placeholder
|
||||
pn = st.add(snapshots.Info{Name: info.Parent})
|
||||
}
|
||||
|
||||
pn.children = append(pn.children, info.Name)
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
func (st *snapshotTree) get(name string) *snapshotTreeNode {
|
||||
return st.index[name]
|
||||
}
|
||||
|
||||
func printTree(st *snapshotTree) {
|
||||
for _, node := range st.nodes {
|
||||
// Print for root(parent-less) nodes only
|
||||
if node.info.Parent == "" {
|
||||
printNode(node.info.Name, st, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printNode(name string, tree *snapshotTree, level int) {
|
||||
node := tree.index[name]
|
||||
prefix := strings.Repeat(" ", level)
|
||||
|
||||
if level > 0 {
|
||||
prefix += "\\_"
|
||||
}
|
||||
|
||||
fmt.Printf(prefix+" %s\n", node.info.Name)
|
||||
level++
|
||||
for _, child := range node.children {
|
||||
printNode(child, tree, level)
|
||||
}
|
||||
}
|
||||
|
||||
func printMounts(target string, mounts []mount.Mount) {
|
||||
// FIXME: This is specific to Unix
|
||||
for _, m := range mounts {
|
||||
fmt.Printf("mount -t %s %s %s -o %s\n", m.Type, m.Source, target, strings.Join(m.Options, ","))
|
||||
}
|
||||
}
|
||||
86
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/attach.go
generated
vendored
Normal file
86
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/attach.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var attachCommand = cli.Command{
|
||||
Name: "attach",
|
||||
Usage: "attach to the IO of a running container",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, err := container.Spec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
con console.Console
|
||||
tty = spec.Process.Terminal
|
||||
)
|
||||
if tty {
|
||||
con = console.Current()
|
||||
defer con.Reset()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
task, err := container.Task(ctx, cio.NewAttach(cio.WithStdio))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer task.Delete(ctx)
|
||||
|
||||
statusC, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tty {
|
||||
if err := HandleConsoleResize(ctx, task, con); err != nil {
|
||||
logrus.WithError(err).Error("console resize")
|
||||
}
|
||||
} else {
|
||||
sigc := commands.ForwardAllSignals(ctx, task)
|
||||
defer commands.StopCatch(sigc)
|
||||
}
|
||||
|
||||
ec := <-statusC
|
||||
code, _, err := ec.Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return cli.NewExitError("", int(code))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
67
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/checkpoint.go
generated
vendored
Normal file
67
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/checkpoint.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var checkpointCommand = cli.Command{
|
||||
Name: "checkpoint",
|
||||
Usage: "checkpoint a container",
|
||||
ArgsUsage: "[flags] CONTAINER",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "exit",
|
||||
Usage: "stop the container after the checkpoint",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var opts []containerd.CheckpointTaskOpts
|
||||
if context.Bool("exit") {
|
||||
opts = append(opts, containerd.WithExit)
|
||||
}
|
||||
checkpoint, err := task.Checkpoint(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(checkpoint.Name())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
63
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/delete.go
generated
vendored
Normal file
63
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/delete.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "[flags] delete a task",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "force delete task process",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var opts []containerd.ProcessDeleteOpts
|
||||
if context.Bool("force") {
|
||||
opts = append(opts, containerd.WithProcessKill)
|
||||
}
|
||||
status, err := task.Delete(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ec := status.ExitCode(); ec != 0 {
|
||||
return cli.NewExitError("", int(ec))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
129
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/exec.go
generated
vendored
Normal file
129
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/exec.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
//TODO:(jessvalarezo) exec-id is optional here, update to required arg
|
||||
var execCommand = cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "execute additional processes in an existing container",
|
||||
ArgsUsage: "[flags] CONTAINER CMD [ARG...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "working directory of the new process",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty,t",
|
||||
Usage: "allocate a TTY for the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "exec-id",
|
||||
Usage: "exec specific id for the process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "fifo-dir",
|
||||
Usage: "directory used for storing IO FIFOs",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
id = context.Args().First()
|
||||
args = context.Args().Tail()
|
||||
tty = context.Bool("tty")
|
||||
)
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, err := container.Spec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pspec := spec.Process
|
||||
pspec.Terminal = tty
|
||||
pspec.Args = args
|
||||
|
||||
cioOpts := []cio.Opt{cio.WithStdio, cio.WithFIFODir(context.String("fifo-dir"))}
|
||||
if tty {
|
||||
cioOpts = append(cioOpts, cio.WithTerminal)
|
||||
}
|
||||
ioCreator := cio.NewCreator(cioOpts...)
|
||||
process, err := task.Exec(ctx, context.String("exec-id"), pspec, ioCreator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer process.Delete(ctx)
|
||||
|
||||
statusC, err := process.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var con console.Console
|
||||
if tty {
|
||||
con = console.Current()
|
||||
defer con.Reset()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tty {
|
||||
if err := HandleConsoleResize(ctx, process, con); err != nil {
|
||||
logrus.WithError(err).Error("console resize")
|
||||
}
|
||||
} else {
|
||||
sigc := commands.ForwardAllSignals(ctx, process)
|
||||
defer commands.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if err := process.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
status := <-statusC
|
||||
code, _, err := status.Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return cli.NewExitError("", int(code))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
83
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/kill.go
generated
vendored
Normal file
83
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/kill.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var killCommand = cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "signal a container (default: SIGTERM)",
|
||||
ArgsUsage: "[flags] CONTAINER",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "signal, s",
|
||||
Value: "SIGTERM",
|
||||
Usage: "signal to send to the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "exec-id",
|
||||
Usage: "process ID to kill",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "send signal to all processes inside the container",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
signal, err := commands.ParseSignal(context.String("signal"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
all = context.Bool("all")
|
||||
execID = context.String("exec-id")
|
||||
opts []containerd.KillOpts
|
||||
)
|
||||
if all && execID != "" {
|
||||
return errors.New("specify an exec-id or all; not both")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if all {
|
||||
opts = append(opts, containerd.WithKillAll)
|
||||
}
|
||||
if execID != "" {
|
||||
opts = append(opts, containerd.WithKillExecID(execID))
|
||||
}
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return task.Kill(ctx, signal, opts...)
|
||||
},
|
||||
}
|
||||
71
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/list.go
generated
vendored
Normal file
71
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/list.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "list tasks",
|
||||
Aliases: []string{"ls"},
|
||||
ArgsUsage: "[flags]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the task id & pid",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
quiet := context.Bool("quiet")
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
s := client.TaskService()
|
||||
response, err := s.List(ctx, &tasks.ListTasksRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if quiet {
|
||||
for _, task := range response.Tasks {
|
||||
fmt.Println(task.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "TASK\tPID\tSTATUS\t")
|
||||
for _, task := range response.Tasks {
|
||||
if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n",
|
||||
task.ID,
|
||||
task.Pid,
|
||||
task.Status.String(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
44
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/pause.go
generated
vendored
Normal file
44
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/pause.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var pauseCommand = cli.Command{
|
||||
Name: "pause",
|
||||
Usage: "pause an existing container",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return task.Pause(ctx)
|
||||
},
|
||||
}
|
||||
72
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/ps.go
generated
vendored
Normal file
72
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/ps.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/typeurl"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var psCommand = cli.Command{
|
||||
Name: "ps",
|
||||
Usage: "list processes for container",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processes, err := task.Pids(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 1, 8, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "PID\tINFO")
|
||||
for _, ps := range processes {
|
||||
var info interface{} = "-"
|
||||
if ps.Info != nil {
|
||||
info, err = typeurl.UnmarshalAny(ps.Info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%d\t%+v\n", ps.Pid, info); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
44
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/resume.go
generated
vendored
Normal file
44
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/resume.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var resumeCommand = cli.Command{
|
||||
Name: "resume",
|
||||
Usage: "resume a paused container",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, context.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return task.Resume(ctx)
|
||||
},
|
||||
}
|
||||
122
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/start.go
generated
vendored
Normal file
122
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/start.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var startCommand = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start a container that have been created",
|
||||
ArgsUsage: "CONTAINER",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "null-io",
|
||||
Usage: "send all IO to /dev/null",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "fifo-dir",
|
||||
Usage: "directory used for storing IO FIFOs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Usage: "file path to write the task's pid",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
err error
|
||||
id = context.Args().Get(0)
|
||||
)
|
||||
if id == "" {
|
||||
return errors.New("container id must be provided")
|
||||
}
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
container, err := client.LoadContainer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec, err := container.Spec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
tty = spec.Process.Terminal
|
||||
opts = getNewTaskOpts(context)
|
||||
ioOpts = []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))}
|
||||
)
|
||||
task, err := NewTask(ctx, client, container, "", tty, context.Bool("null-io"), ioOpts, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer task.Delete(ctx)
|
||||
if context.IsSet("pid-file") {
|
||||
if err := commands.WritePidFile(context.String("pid-file"), int(task.Pid())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
statusC, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var con console.Console
|
||||
if tty {
|
||||
con = console.Current()
|
||||
defer con.Reset()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := task.Start(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if tty {
|
||||
if err := HandleConsoleResize(ctx, task, con); err != nil {
|
||||
logrus.WithError(err).Error("console resize")
|
||||
}
|
||||
} else {
|
||||
sigc := commands.ForwardAllSignals(ctx, task)
|
||||
defer commands.StopCatch(sigc)
|
||||
}
|
||||
|
||||
status := <-statusC
|
||||
code, _, err := status.Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return cli.NewExitError("", int(code))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
46
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks.go
generated
vendored
Normal file
46
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type resizer interface {
|
||||
Resize(ctx gocontext.Context, w, h uint32) error
|
||||
}
|
||||
|
||||
// Command is the cli command for managing tasks
|
||||
var Command = cli.Command{
|
||||
Name: "tasks",
|
||||
Usage: "manage tasks",
|
||||
Aliases: []string{"t", "task"},
|
||||
Subcommands: []cli.Command{
|
||||
attachCommand,
|
||||
checkpointCommand,
|
||||
deleteCommand,
|
||||
execCommand,
|
||||
listCommand,
|
||||
killCommand,
|
||||
pauseCommand,
|
||||
psCommand,
|
||||
resumeCommand,
|
||||
startCommand,
|
||||
},
|
||||
}
|
||||
98
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks_unix.go
generated
vendored
Normal file
98
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks_unix.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// +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 tasks
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
startCommand.Flags = append(startCommand.Flags, cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "disable use of pivot-root (linux only)",
|
||||
})
|
||||
}
|
||||
|
||||
// HandleConsoleResize resizes the console
|
||||
func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error {
|
||||
// do an initial resize of the console
|
||||
size, err := con.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||
log.G(ctx).WithError(err).Error("resize pty")
|
||||
}
|
||||
s := make(chan os.Signal, 16)
|
||||
signal.Notify(s, unix.SIGWINCH)
|
||||
go func() {
|
||||
for range s {
|
||||
size, err := con.Size()
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Error("get pty size")
|
||||
continue
|
||||
}
|
||||
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||
log.G(ctx).WithError(err).Error("resize pty")
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTask creates a new task
|
||||
func NewTask(ctx gocontext.Context, client *containerd.Client, container containerd.Container, checkpoint string, tty, nullIO bool, ioOpts []cio.Opt, opts ...containerd.NewTaskOpts) (containerd.Task, error) {
|
||||
stdio := cio.NewCreator(append([]cio.Opt{cio.WithStdio}, ioOpts...)...)
|
||||
if checkpoint == "" {
|
||||
ioCreator := stdio
|
||||
if tty {
|
||||
ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStdio, cio.WithTerminal}, ioOpts...)...)
|
||||
}
|
||||
if nullIO {
|
||||
if tty {
|
||||
return nil, errors.New("tty and null-io cannot be used together")
|
||||
}
|
||||
ioCreator = cio.NullIO
|
||||
}
|
||||
return container.NewTask(ctx, ioCreator, opts...)
|
||||
}
|
||||
im, err := client.GetImage(ctx, checkpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, containerd.WithTaskCheckpoint(im))
|
||||
return container.NewTask(ctx, stdio, opts...)
|
||||
}
|
||||
|
||||
func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts {
|
||||
if context.Bool("no-pivot") {
|
||||
return []containerd.NewTaskOpts{containerd.WithNoPivotRoot}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
77
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks_windows.go
generated
vendored
Normal file
77
vendor/github.com/containerd/containerd/cmd/ctr/commands/tasks/tasks_windows.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 tasks
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// HandleConsoleResize resizes the console
|
||||
func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error {
|
||||
// do an initial resize of the console
|
||||
size, err := con.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
prevSize := size
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
|
||||
size, err := con.Size()
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Error("get pty size")
|
||||
continue
|
||||
}
|
||||
|
||||
if size.Width != prevSize.Width || size.Height != prevSize.Height {
|
||||
if err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {
|
||||
log.G(ctx).WithError(err).Error("resize pty")
|
||||
}
|
||||
prevSize = size
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTask creates a new task
|
||||
func NewTask(ctx gocontext.Context, client *containerd.Client, container containerd.Container, _ string, tty, nullIO bool, ioOpts []cio.Opt, opts ...containerd.NewTaskOpts) (containerd.Task, error) {
|
||||
ioCreator := cio.NewCreator(append([]cio.Opt{cio.WithStdio}, ioOpts...)...)
|
||||
if tty {
|
||||
ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStdio, cio.WithTerminal}, ioOpts...)...)
|
||||
}
|
||||
if nullIO {
|
||||
if tty {
|
||||
return nil, errors.New("tty and null-io cannot be used together")
|
||||
}
|
||||
ioCreator = cio.NullIO
|
||||
}
|
||||
return container.NewTask(ctx, ioCreator)
|
||||
}
|
||||
|
||||
func getNewTaskOpts(_ *cli.Context) []containerd.NewTaskOpts {
|
||||
return nil
|
||||
}
|
||||
57
vendor/github.com/containerd/containerd/cmd/ctr/commands/version/version.go
generated
vendored
Normal file
57
vendor/github.com/containerd/containerd/cmd/ctr/commands/version/version.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/version"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is a cli command to output the client and containerd server version
|
||||
var Command = cli.Command{
|
||||
Name: "version",
|
||||
Usage: "print the client and server versions",
|
||||
Action: func(context *cli.Context) error {
|
||||
fmt.Println("Client:")
|
||||
fmt.Println(" Version: ", version.Version)
|
||||
fmt.Println(" Revision:", version.Revision)
|
||||
fmt.Println("")
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
v, err := client.Version(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Server:")
|
||||
fmt.Println(" Version: ", v.Version)
|
||||
fmt.Println(" Revision:", v.Revision)
|
||||
if v.Version != version.Version {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: version mismatch")
|
||||
}
|
||||
if v.Revision != version.Revision {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: revision mismatch")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
18
vendor/github.com/containerd/containerd/content/helpers.go
generated
vendored
18
vendor/github.com/containerd/containerd/content/helpers.go
generated
vendored
@@ -78,7 +78,7 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, size i
|
||||
}
|
||||
|
||||
// Copy copies data with the expected digest from the reader into the
|
||||
// provided content store writer.
|
||||
// provided content store writer. This copy commits the writer.
|
||||
//
|
||||
// This is useful when the digest and size are known beforehand. When
|
||||
// the size or digest is unknown, these values may be empty.
|
||||
@@ -113,6 +113,22 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyReaderAt copies to a writer from a given reader at for the given
|
||||
// number of bytes. This copy does not commit the writer.
|
||||
func CopyReaderAt(cw Writer, ra ReaderAt, n int64) error {
|
||||
ws, err := cw.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := bufPool.Get().(*[]byte)
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
_, err = io.CopyBuffer(cw, io.NewSectionReader(ra, ws.Offset, n), *buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// seekReader attempts to seek the reader to the given offset, either by
|
||||
// resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding
|
||||
// up to the given offset.
|
||||
|
||||
41
vendor/github.com/containerd/containerd/images/handlers.go
generated
vendored
41
vendor/github.com/containerd/containerd/images/handlers.go
generated
vendored
@@ -182,9 +182,9 @@ func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// FilterPlatform is a handler wrapper which limits the descriptors returned
|
||||
// by a handler to a single platform.
|
||||
func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
|
||||
// FilterPlatforms is a handler wrapper which limits the descriptors returned
|
||||
// by a handler to the specified platforms.
|
||||
func FilterPlatforms(f HandlerFunc, platformList ...string) HandlerFunc {
|
||||
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := f(ctx, desc)
|
||||
if err != nil {
|
||||
@@ -192,32 +192,25 @@ func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
|
||||
}
|
||||
|
||||
var descs []ocispec.Descriptor
|
||||
if platform != "" && isMultiPlatform(desc.MediaType) {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcher := platforms.NewMatcher(p)
|
||||
|
||||
for _, d := range children {
|
||||
if d.Platform == nil || matcher.Match(*d.Platform) {
|
||||
descs = append(descs, d)
|
||||
if len(platformList) == 0 {
|
||||
descs = children
|
||||
} else {
|
||||
for _, platform := range platformList {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcher := platforms.NewMatcher(p)
|
||||
|
||||
for _, d := range children {
|
||||
if d.Platform == nil || matcher.Match(*d.Platform) {
|
||||
descs = append(descs, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
descs = children
|
||||
}
|
||||
|
||||
return descs, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func isMultiPlatform(mediaType string) bool {
|
||||
switch mediaType {
|
||||
case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/containerd/containerd/images/image.go
generated
vendored
2
vendor/github.com/containerd/containerd/images/image.go
generated
vendored
@@ -118,7 +118,7 @@ func (image *Image) Size(ctx context.Context, provider content.Provider, platfor
|
||||
}
|
||||
size += desc.Size
|
||||
return nil, nil
|
||||
}), FilterPlatform(platform, ChildrenHandler(provider))), image.Target)
|
||||
}), FilterPlatforms(ChildrenHandler(provider), platform)), image.Target)
|
||||
}
|
||||
|
||||
// Manifest resolves a manifest from the image for the given platform.
|
||||
|
||||
204
vendor/github.com/containerd/containerd/images/oci/exporter.go
generated
vendored
Normal file
204
vendor/github.com/containerd/containerd/images/oci/exporter.go
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
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 oci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// V1Exporter implements OCI Image Spec v1.
|
||||
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
||||
//
|
||||
// TODO(AkihiroSuda): add V1Exporter{TranslateMediaTypes: true} that transforms media types,
|
||||
// e.g. application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
// -> application/vnd.oci.image.layer.v1.tar+gzip
|
||||
type V1Exporter struct {
|
||||
}
|
||||
|
||||
// Export implements Exporter.
|
||||
func (oe *V1Exporter) Export(ctx context.Context, store content.Provider, desc ocispec.Descriptor, writer io.Writer) error {
|
||||
tw := tar.NewWriter(writer)
|
||||
defer tw.Close()
|
||||
|
||||
records := []tarRecord{
|
||||
ociLayoutFile(""),
|
||||
ociIndexRecord(desc),
|
||||
}
|
||||
|
||||
algorithms := map[string]struct{}{}
|
||||
exportHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
records = append(records, blobRecord(store, desc))
|
||||
algorithms[desc.Digest.Algorithm().String()] = struct{}{}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
handlers := images.Handlers(
|
||||
images.FilterPlatforms(images.ChildrenHandler(store), platforms.Default()),
|
||||
images.HandlerFunc(exportHandler),
|
||||
)
|
||||
|
||||
// Walk sequentially since the number of fetchs is likely one and doing in
|
||||
// parallel requires locking the export handler
|
||||
if err := images.Walk(ctx, handlers, desc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(algorithms) > 0 {
|
||||
records = append(records, directoryRecord("blobs/", 0755))
|
||||
for alg := range algorithms {
|
||||
records = append(records, directoryRecord("blobs/"+alg+"/", 0755))
|
||||
}
|
||||
}
|
||||
|
||||
return writeTar(ctx, tw, records)
|
||||
}
|
||||
|
||||
type tarRecord struct {
|
||||
Header *tar.Header
|
||||
CopyTo func(context.Context, io.Writer) (int64, error)
|
||||
}
|
||||
|
||||
func blobRecord(cs content.Provider, desc ocispec.Descriptor) tarRecord {
|
||||
path := "blobs/" + desc.Digest.Algorithm().String() + "/" + desc.Digest.Hex()
|
||||
return tarRecord{
|
||||
Header: &tar.Header{
|
||||
Name: path,
|
||||
Mode: 0444,
|
||||
Size: desc.Size,
|
||||
Typeflag: tar.TypeReg,
|
||||
},
|
||||
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||
r, err := cs.ReaderAt(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Verify digest
|
||||
dgstr := desc.Digest.Algorithm().Digester()
|
||||
|
||||
n, err := io.Copy(io.MultiWriter(w, dgstr.Hash()), content.NewReader(r))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dgstr.Digest() != desc.Digest {
|
||||
return 0, errors.Errorf("unexpected digest %s copied", dgstr.Digest())
|
||||
}
|
||||
return n, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func directoryRecord(name string, mode int64) tarRecord {
|
||||
return tarRecord{
|
||||
Header: &tar.Header{
|
||||
Name: name,
|
||||
Mode: mode,
|
||||
Typeflag: tar.TypeDir,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ociLayoutFile(version string) tarRecord {
|
||||
if version == "" {
|
||||
version = ocispec.ImageLayoutVersion
|
||||
}
|
||||
layout := ocispec.ImageLayout{
|
||||
Version: version,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(layout)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tarRecord{
|
||||
Header: &tar.Header{
|
||||
Name: ocispec.ImageLayoutFile,
|
||||
Mode: 0444,
|
||||
Size: int64(len(b)),
|
||||
Typeflag: tar.TypeReg,
|
||||
},
|
||||
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||
n, err := w.Write(b)
|
||||
return int64(n), err
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ociIndexRecord(manifests ...ocispec.Descriptor) tarRecord {
|
||||
index := ocispec.Index{
|
||||
Versioned: ocispecs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Manifests: manifests,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tarRecord{
|
||||
Header: &tar.Header{
|
||||
Name: "index.json",
|
||||
Mode: 0644,
|
||||
Size: int64(len(b)),
|
||||
Typeflag: tar.TypeReg,
|
||||
},
|
||||
CopyTo: func(ctx context.Context, w io.Writer) (int64, error) {
|
||||
n, err := w.Write(b)
|
||||
return int64(n), err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func writeTar(ctx context.Context, tw *tar.Writer, records []tarRecord) error {
|
||||
sort.Slice(records, func(i, j int) bool {
|
||||
return records[i].Header.Name < records[j].Header.Name
|
||||
})
|
||||
|
||||
for _, record := range records {
|
||||
if err := tw.WriteHeader(record.Header); err != nil {
|
||||
return err
|
||||
}
|
||||
if record.CopyTo != nil {
|
||||
n, err := record.CopyTo(ctx, tw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != record.Header.Size {
|
||||
return errors.Errorf("unexpected copy size for %s", record.Header.Name)
|
||||
}
|
||||
} else if record.Header.Size > 0 {
|
||||
return errors.Errorf("no content to write to record with non-zero size for %s", record.Header.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
204
vendor/github.com/containerd/containerd/images/oci/importer.go
generated
vendored
Normal file
204
vendor/github.com/containerd/containerd/images/oci/importer.go
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
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 oci provides the importer and the exporter for OCI Image Spec.
|
||||
package oci
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// V1Importer implements OCI Image Spec v1.
|
||||
type V1Importer struct {
|
||||
// ImageName is preprended to either `:` + OCI ref name or `@` + digest (for anonymous refs).
|
||||
// This field is mandatory atm, but may change in the future. maybe ref map[string]string as in moby/moby#33355
|
||||
ImageName string
|
||||
}
|
||||
|
||||
var _ images.Importer = &V1Importer{}
|
||||
|
||||
// Import implements Importer.
|
||||
func (oi *V1Importer) Import(ctx context.Context, store content.Store, reader io.Reader) ([]images.Image, error) {
|
||||
if oi.ImageName == "" {
|
||||
return nil, errors.New("ImageName not set")
|
||||
}
|
||||
tr := tar.NewReader(reader)
|
||||
var imgrecs []images.Image
|
||||
foundIndexJSON := false
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
|
||||
continue
|
||||
}
|
||||
hdrName := path.Clean(hdr.Name)
|
||||
if hdrName == "index.json" {
|
||||
if foundIndexJSON {
|
||||
return nil, errors.New("duplicated index.json")
|
||||
}
|
||||
foundIndexJSON = true
|
||||
imgrecs, err = onUntarIndexJSON(tr, oi.ImageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(hdrName, "blobs/") {
|
||||
if err := onUntarBlob(ctx, tr, store, hdrName, hdr.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundIndexJSON {
|
||||
return nil, errors.New("no index.json found")
|
||||
}
|
||||
for _, img := range imgrecs {
|
||||
err := setGCRefContentLabels(ctx, store, img.Target)
|
||||
if err != nil {
|
||||
return imgrecs, err
|
||||
}
|
||||
}
|
||||
// FIXME(AkihiroSuda): set GC labels for unreferrenced blobs (i.e. with unknown media types)?
|
||||
return imgrecs, nil
|
||||
}
|
||||
|
||||
func onUntarIndexJSON(r io.Reader, imageName string) ([]images.Image, error) {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var idx ocispec.Index
|
||||
if err := json.Unmarshal(b, &idx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var imgrecs []images.Image
|
||||
for _, m := range idx.Manifests {
|
||||
ref, err := normalizeImageRef(imageName, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imgrecs = append(imgrecs, images.Image{
|
||||
Name: ref,
|
||||
Target: m,
|
||||
})
|
||||
}
|
||||
return imgrecs, nil
|
||||
}
|
||||
|
||||
func normalizeImageRef(imageName string, manifest ocispec.Descriptor) (string, error) {
|
||||
digest := manifest.Digest
|
||||
if digest == "" {
|
||||
return "", errors.Errorf("manifest with empty digest: %v", manifest)
|
||||
}
|
||||
ociRef := manifest.Annotations[ocispec.AnnotationRefName]
|
||||
if ociRef == "" {
|
||||
return imageName + "@" + digest.String(), nil
|
||||
}
|
||||
return imageName + ":" + ociRef, nil
|
||||
}
|
||||
|
||||
func onUntarBlob(ctx context.Context, r io.Reader, store content.Ingester, name string, size int64) error {
|
||||
// name is like "blobs/sha256/deadbeef"
|
||||
split := strings.Split(name, "/")
|
||||
if len(split) != 3 {
|
||||
return errors.Errorf("unexpected name: %q", name)
|
||||
}
|
||||
algo := digest.Algorithm(split[1])
|
||||
if !algo.Available() {
|
||||
return errors.Errorf("unsupported algorithm: %s", algo)
|
||||
}
|
||||
dgst := digest.NewDigestFromHex(algo.String(), split[2])
|
||||
return content.WriteBlob(ctx, store, "unknown-"+dgst.String(), r, size, dgst)
|
||||
}
|
||||
|
||||
// GetChildrenDescriptors returns children blob descriptors for the following supported types:
|
||||
// - images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest
|
||||
// - images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex
|
||||
func GetChildrenDescriptors(r io.Reader, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
switch desc.MediaType {
|
||||
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
||||
var manifest ocispec.Manifest
|
||||
if err := json.NewDecoder(r).Decode(&manifest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]ocispec.Descriptor{manifest.Config}, manifest.Layers...), nil
|
||||
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
var index ocispec.Index
|
||||
if err := json.NewDecoder(r).Decode(&index); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return index.Manifests, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func setGCRefContentLabels(ctx context.Context, store content.Store, desc ocispec.Descriptor) error {
|
||||
info, err := store.Info(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
// when the archive is created from multi-arch image,
|
||||
// it may contain only blobs for a certain platform.
|
||||
// So ErrNotFound (on manifest list) is expected here.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
ra, err := store.ReaderAt(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ra.Close()
|
||||
r := content.NewReader(ra)
|
||||
children, err := GetChildrenDescriptors(r, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Labels == nil {
|
||||
info.Labels = map[string]string{}
|
||||
}
|
||||
for i, child := range children {
|
||||
// Note: child blob is not guaranteed to be written to the content store. (multi-arch)
|
||||
info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = child.Digest.String()
|
||||
}
|
||||
if _, err := store.Update(ctx, info, "labels"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, child := range children {
|
||||
if err := setGCRefContentLabels(ctx, store, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/containerd/containerd/metadata/buckets.go
generated
vendored
18
vendor/github.com/containerd/containerd/metadata/buckets.go
generated
vendored
@@ -52,7 +52,7 @@ var (
|
||||
bucketKeyObjectSnapshots = []byte("snapshots") // stores snapshot references
|
||||
bucketKeyObjectContent = []byte("content") // stores content references
|
||||
bucketKeyObjectBlob = []byte("blob") // stores content links
|
||||
bucketKeyObjectIngest = []byte("ingest") // stores ingest links
|
||||
bucketKeyObjectIngests = []byte("ingests") // stores ingest objects
|
||||
bucketKeyObjectLeases = []byte("leases") // stores leases
|
||||
|
||||
bucketKeyDigest = []byte("digest")
|
||||
@@ -70,6 +70,10 @@ var (
|
||||
bucketKeyTarget = []byte("target")
|
||||
bucketKeyExtensions = []byte("extensions")
|
||||
bucketKeyCreatedAt = []byte("createdat")
|
||||
bucketKeyExpected = []byte("expected")
|
||||
bucketKeyRef = []byte("ref")
|
||||
|
||||
deprecatedBucketKeyObjectIngest = []byte("ingest") // stores ingest links, deprecated in v1.2
|
||||
)
|
||||
|
||||
func getBucket(tx *bolt.Tx, keys ...[]byte) *bolt.Bucket {
|
||||
@@ -178,14 +182,18 @@ func getBlobBucket(tx *bolt.Tx, namespace string, dgst digest.Digest) *bolt.Buck
|
||||
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(dgst.String()))
|
||||
}
|
||||
|
||||
func createIngestBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
|
||||
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngest)
|
||||
func getIngestsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
|
||||
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests)
|
||||
}
|
||||
|
||||
func createIngestBucket(tx *bolt.Tx, namespace, ref string) (*bolt.Bucket, error) {
|
||||
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(ref))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bkt, nil
|
||||
}
|
||||
|
||||
func getIngestBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
|
||||
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngest)
|
||||
func getIngestBucket(tx *bolt.Tx, namespace, ref string) *bolt.Bucket {
|
||||
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(ref))
|
||||
}
|
||||
|
||||
246
vendor/github.com/containerd/containerd/metadata/content.go
generated
vendored
246
vendor/github.com/containerd/containerd/metadata/content.go
generated
vendored
@@ -226,14 +226,16 @@ func (cs *contentStore) ListStatuses(ctx context.Context, fs ...string) ([]conte
|
||||
|
||||
brefs := map[string]string{}
|
||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
bkt := getIngestsBucket(tx, ns)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
// TODO(dmcgowan): match name and potentially labels here
|
||||
brefs[string(k)] = string(v)
|
||||
if v == nil {
|
||||
// TODO(dmcgowan): match name and potentially labels here
|
||||
brefs[string(k)] = string(bkt.Bucket(k).Get(bucketKeyRef))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
@@ -261,11 +263,11 @@ func (cs *contentStore) ListStatuses(ctx context.Context, fs ...string) ([]conte
|
||||
}
|
||||
|
||||
func getRef(tx *bolt.Tx, ns, ref string) string {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
bkt := getIngestBucket(tx, ns, ref)
|
||||
if bkt == nil {
|
||||
return ""
|
||||
}
|
||||
v := bkt.Get([]byte(ref))
|
||||
v := bkt.Get(bucketKeyRef)
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -308,19 +310,29 @@ func (cs *contentStore) Abort(ctx context.Context, ref string) error {
|
||||
defer cs.l.RUnlock()
|
||||
|
||||
return update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, ns)
|
||||
ibkt := getIngestsBucket(tx, ns)
|
||||
if ibkt == nil {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
bkt := ibkt.Bucket([]byte(ref))
|
||||
if bkt == nil {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
bref := string(bkt.Get([]byte(ref)))
|
||||
bref := string(bkt.Get(bucketKeyRef))
|
||||
if bref == "" {
|
||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||
}
|
||||
if err := bkt.Delete([]byte(ref)); err != nil {
|
||||
expected := string(bkt.Get(bucketKeyExpected))
|
||||
if err := ibkt.DeleteBucket([]byte(ref)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cs.Store.Abort(ctx, bref)
|
||||
// if not shared content, delete active ingest on backend
|
||||
if expected == "" {
|
||||
return cs.Store.Abort(ctx, bref)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -337,8 +349,10 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
var (
|
||||
w content.Writer
|
||||
exists bool
|
||||
bref string
|
||||
)
|
||||
if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||
var shared bool
|
||||
if expected != "" {
|
||||
cbkt := getBlobBucket(tx, ns, expected)
|
||||
if cbkt != nil {
|
||||
@@ -352,18 +366,24 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
exists = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if st, err := cs.Store.Info(ctx, expected); err == nil {
|
||||
// Ensure the expected size is the same, it is likely
|
||||
// an error if the size is mismatched but the caller
|
||||
// must resolve this on commit
|
||||
if size == 0 || size == st.Size {
|
||||
shared = true
|
||||
size = st.Size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bkt, err := createIngestBucket(tx, ns)
|
||||
bkt, err := createIngestBucket(tx, ns, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
bref string
|
||||
brefb = bkt.Get([]byte(ref))
|
||||
)
|
||||
|
||||
brefb := bkt.Get(bucketKeyRef)
|
||||
if brefb == nil {
|
||||
sid, err := bkt.NextSequence()
|
||||
if err != nil {
|
||||
@@ -371,21 +391,24 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
}
|
||||
|
||||
bref = createKey(sid, ns, ref)
|
||||
if err := bkt.Put([]byte(ref), []byte(bref)); err != nil {
|
||||
if err := bkt.Put(bucketKeyRef, []byte(bref)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
bref = string(brefb)
|
||||
}
|
||||
|
||||
// Do not use the passed in expected value here since it was
|
||||
// already checked against the user metadata. If the content
|
||||
// store has the content, it must still be written before
|
||||
// linked into the given namespace. It is possible in the future
|
||||
// to allow content which exists in content store but not
|
||||
// namespace to be linked here and returned an exist error, but
|
||||
// this would require more configuration to make secure.
|
||||
w, err = cs.Store.Writer(ctx, bref, size, "")
|
||||
if shared {
|
||||
if err := bkt.Put(bucketKeyExpected, []byte(expected)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Do not use the passed in expected value here since it was
|
||||
// already checked against the user metadata. The content must
|
||||
// be committed in the namespace before it will be seen as
|
||||
// available in the current namespace.
|
||||
w, err = cs.Store.Writer(ctx, bref, size, "")
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
@@ -394,23 +417,99 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
||||
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
|
||||
}
|
||||
|
||||
// TODO: keep the expected in the writer to use on commit
|
||||
// when no expected is provided there.
|
||||
return &namespacedWriter{
|
||||
Writer: w,
|
||||
ctx: ctx,
|
||||
ref: ref,
|
||||
namespace: ns,
|
||||
db: cs.db,
|
||||
provider: cs.Store,
|
||||
l: &cs.l,
|
||||
w: w,
|
||||
bref: bref,
|
||||
started: time.Now(),
|
||||
expected: expected,
|
||||
size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type namespacedWriter struct {
|
||||
content.Writer
|
||||
ctx context.Context
|
||||
ref string
|
||||
namespace string
|
||||
db transactor
|
||||
l *sync.RWMutex
|
||||
provider interface {
|
||||
content.Provider
|
||||
content.Ingester
|
||||
}
|
||||
l *sync.RWMutex
|
||||
|
||||
w content.Writer
|
||||
|
||||
bref string
|
||||
started time.Time
|
||||
expected digest.Digest
|
||||
size int64
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Close() error {
|
||||
if nw.w != nil {
|
||||
return nw.w.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Write(p []byte) (int, error) {
|
||||
// if no writer, first copy and unshare before performing write
|
||||
if nw.w == nil {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if err := nw.createAndCopy(nw.ctx, nw.size); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return nw.w.Write(p)
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Digest() digest.Digest {
|
||||
if nw.w != nil {
|
||||
return nw.w.Digest()
|
||||
}
|
||||
return nw.expected
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Truncate(size int64) error {
|
||||
if nw.w != nil {
|
||||
return nw.w.Truncate(size)
|
||||
}
|
||||
|
||||
return nw.createAndCopy(nw.ctx, size)
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) createAndCopy(ctx context.Context, size int64) error {
|
||||
w, err := nw.provider.Writer(ctx, nw.bref, nw.size, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
ra, err := nw.provider.ReaderAt(ctx, nw.expected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
if err := content.CopyReaderAt(w, ra, size); err != nil {
|
||||
nw.w.Close()
|
||||
nw.w = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
nw.w = w
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
|
||||
@@ -418,9 +517,9 @@ func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected dig
|
||||
defer nw.l.RUnlock()
|
||||
|
||||
return update(ctx, nw.db, func(tx *bolt.Tx) error {
|
||||
bkt := getIngestBucket(tx, nw.namespace)
|
||||
bkt := getIngestsBucket(tx, nw.namespace)
|
||||
if bkt != nil {
|
||||
if err := bkt.Delete([]byte(nw.ref)); err != nil {
|
||||
if err := bkt.DeleteBucket([]byte(nw.ref)); err != nil && err != bolt.ErrBucketNotFound {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -443,24 +542,38 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
||||
return "", err
|
||||
}
|
||||
|
||||
status, err := nw.Writer.Status()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if size != 0 && size != status.Offset {
|
||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
||||
}
|
||||
size = status.Offset
|
||||
|
||||
actual := nw.Writer.Digest()
|
||||
|
||||
if err := nw.Writer.Commit(ctx, size, expected); err != nil {
|
||||
if !errdefs.IsAlreadyExists(err) {
|
||||
return "", err
|
||||
var actual digest.Digest
|
||||
if nw.w == nil {
|
||||
if size != 0 && size != nw.size {
|
||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, nw.size, size)
|
||||
}
|
||||
if expected != "" && expected != nw.expected {
|
||||
return "", errors.Errorf("%q unexpected digest", nw.ref)
|
||||
}
|
||||
size = nw.size
|
||||
actual = nw.expected
|
||||
if getBlobBucket(tx, nw.namespace, actual) != nil {
|
||||
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
|
||||
}
|
||||
} else {
|
||||
status, err := nw.w.Status()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if size != 0 && size != status.Offset {
|
||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
||||
}
|
||||
size = status.Offset
|
||||
actual = nw.w.Digest()
|
||||
|
||||
if err := nw.w.Commit(ctx, size, expected); err != nil {
|
||||
if !errdefs.IsAlreadyExists(err) {
|
||||
return "", err
|
||||
}
|
||||
if getBlobBucket(tx, nw.namespace, actual) != nil {
|
||||
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bkt, err := createBlobBucket(tx, nw.namespace, actual)
|
||||
@@ -484,12 +597,20 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
||||
return actual, bkt.Put(bucketKeySize, sizeEncoded)
|
||||
}
|
||||
|
||||
func (nw *namespacedWriter) Status() (content.Status, error) {
|
||||
st, err := nw.Writer.Status()
|
||||
func (nw *namespacedWriter) Status() (st content.Status, err error) {
|
||||
if nw.w != nil {
|
||||
st, err = nw.w.Status()
|
||||
} else {
|
||||
st.Offset = nw.size
|
||||
st.Total = nw.size
|
||||
st.StartedAt = nw.started
|
||||
st.UpdatedAt = nw.started
|
||||
st.Expected = nw.expected
|
||||
}
|
||||
if err == nil {
|
||||
st.Ref = nw.ref
|
||||
}
|
||||
return st, err
|
||||
return
|
||||
}
|
||||
|
||||
func (cs *contentStore) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) {
|
||||
@@ -590,13 +711,30 @@ func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, er
|
||||
continue
|
||||
}
|
||||
bbkt := cbkt.Bucket(bucketKeyObjectBlob)
|
||||
if err := bbkt.ForEach(func(ck, cv []byte) error {
|
||||
if cv == nil {
|
||||
seen[string(ck)] = struct{}{}
|
||||
if bbkt != nil {
|
||||
if err := bbkt.ForEach(func(ck, cv []byte) error {
|
||||
if cv == nil {
|
||||
seen[string(ck)] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ibkt := cbkt.Bucket(bucketKeyObjectIngests)
|
||||
if ibkt != nil {
|
||||
if err := ibkt.ForEach(func(ref, v []byte) error {
|
||||
if v == nil {
|
||||
expected := ibkt.Bucket(ref).Get(bucketKeyExpected)
|
||||
if len(expected) > 0 {
|
||||
seen[string(expected)] = struct{}{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/containerd/containerd/metadata/db.go
generated
vendored
2
vendor/github.com/containerd/containerd/metadata/db.go
generated
vendored
@@ -43,7 +43,7 @@ const (
|
||||
// dbVersion represents updates to the schema
|
||||
// version which are additions and compatible with
|
||||
// prior version of the same schema.
|
||||
dbVersion = 1
|
||||
dbVersion = 2
|
||||
)
|
||||
|
||||
// DB represents a metadata database backed by a bolt
|
||||
|
||||
55
vendor/github.com/containerd/containerd/metadata/migrations.go
generated
vendored
55
vendor/github.com/containerd/containerd/metadata/migrations.go
generated
vendored
@@ -40,6 +40,11 @@ var migrations = []migration{
|
||||
version: 1,
|
||||
migrate: addChildLinks,
|
||||
},
|
||||
{
|
||||
schema: "v1",
|
||||
version: 2,
|
||||
migrate: migrateIngests,
|
||||
},
|
||||
}
|
||||
|
||||
// addChildLinks Adds children key to the snapshotters to enforce snapshot
|
||||
@@ -99,3 +104,53 @@ func addChildLinks(tx *bolt.Tx) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateIngests moves ingests from the key/value ingest bucket
|
||||
// to a structured ingest bucket for storing additional state about
|
||||
// an ingest.
|
||||
func migrateIngests(tx *bolt.Tx) error {
|
||||
v1bkt := tx.Bucket(bucketKeyVersion)
|
||||
if v1bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// iterate through each namespace
|
||||
v1c := v1bkt.Cursor()
|
||||
|
||||
for k, v := v1c.First(); k != nil; k, v = v1c.Next() {
|
||||
if v != nil {
|
||||
continue
|
||||
}
|
||||
bkt := v1bkt.Bucket(k).Bucket(bucketKeyObjectContent)
|
||||
if bkt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dbkt := bkt.Bucket(deprecatedBucketKeyObjectIngest)
|
||||
if dbkt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new ingests bucket
|
||||
nbkt, err := bkt.CreateBucketIfNotExists(bucketKeyObjectIngests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dbkt.ForEach(func(ref, bref []byte) error {
|
||||
ibkt, err := nbkt.CreateBucketIfNotExists(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ibkt.Put(bucketKeyRef, bref)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bkt.DeleteBucket(deprecatedBucketKeyObjectIngest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
11
vendor/github.com/containerd/containerd/metadata/snapshot.go
generated
vendored
11
vendor/github.com/containerd/containerd/metadata/snapshot.go
generated
vendored
@@ -613,14 +613,23 @@ func validateSnapshot(info *snapshots.Info) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type cleaner interface {
|
||||
Cleanup(ctx context.Context) error
|
||||
}
|
||||
|
||||
func (s *snapshotter) garbageCollect(ctx context.Context) (d time.Duration, err error) {
|
||||
s.l.Lock()
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
s.l.Unlock()
|
||||
if err == nil {
|
||||
if c, ok := s.Snapshotter.(cleaner); ok {
|
||||
err = c.Cleanup(ctx)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
d = time.Now().Sub(t1)
|
||||
}
|
||||
s.l.Unlock()
|
||||
}()
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
2
vendor/github.com/containerd/containerd/namespaces/context.go
generated
vendored
2
vendor/github.com/containerd/containerd/namespaces/context.go
generated
vendored
@@ -17,11 +17,11 @@
|
||||
package namespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
82
vendor/github.com/containerd/containerd/progress/bar.go
generated
vendored
Normal file
82
vendor/github.com/containerd/containerd/progress/bar.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 progress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TODO(stevvooe): We may want to support more interesting parameterization of
|
||||
// the bar. For now, it is very simple.
|
||||
|
||||
// Bar provides a very simple progress bar implementation.
|
||||
//
|
||||
// Use with fmt.Printf and "r" to format the progress bar. A "-" flag makes it
|
||||
// progress from right to left.
|
||||
type Bar float64
|
||||
|
||||
var _ fmt.Formatter = Bar(1.0)
|
||||
|
||||
// Format the progress bar as output
|
||||
func (h Bar) Format(state fmt.State, r rune) {
|
||||
switch r {
|
||||
case 'r':
|
||||
default:
|
||||
panic(fmt.Sprintf("%v: unexpected format character", float64(h)))
|
||||
}
|
||||
|
||||
if h > 1.0 {
|
||||
h = 1.0
|
||||
}
|
||||
|
||||
if h < 0.0 {
|
||||
h = 0.0
|
||||
}
|
||||
|
||||
if state.Flag('-') {
|
||||
h = 1.0 - h
|
||||
}
|
||||
|
||||
width, ok := state.Width()
|
||||
if !ok {
|
||||
// default width of 40
|
||||
width = 40
|
||||
}
|
||||
|
||||
var pad int
|
||||
|
||||
extra := len([]byte(green)) + len([]byte(reset))
|
||||
|
||||
p := make([]byte, width+extra)
|
||||
p[0], p[len(p)-1] = '|', '|'
|
||||
pad += 2
|
||||
|
||||
positive := int(Bar(width-pad) * h)
|
||||
negative := width - pad - positive
|
||||
|
||||
n := 1
|
||||
n += copy(p[n:], []byte(green))
|
||||
n += copy(p[n:], bytes.Repeat([]byte("+"), positive))
|
||||
n += copy(p[n:], []byte(reset))
|
||||
|
||||
if negative > 0 {
|
||||
copy(p[n:len(p)-1], bytes.Repeat([]byte("-"), negative))
|
||||
}
|
||||
|
||||
state.Write(p)
|
||||
}
|
||||
18
vendor/github.com/containerd/containerd/progress/doc.go
generated
vendored
Normal file
18
vendor/github.com/containerd/containerd/progress/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
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 progress assists in displaying human readable progress information.
|
||||
package progress
|
||||
24
vendor/github.com/containerd/containerd/progress/escape.go
generated
vendored
Normal file
24
vendor/github.com/containerd/containerd/progress/escape.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 progress
|
||||
|
||||
const (
|
||||
escape = "\x1b"
|
||||
reset = escape + "[0m"
|
||||
red = escape + "[31m" // nolint: unused, varcheck
|
||||
green = escape + "[32m"
|
||||
)
|
||||
45
vendor/github.com/containerd/containerd/progress/humaans.go
generated
vendored
Normal file
45
vendor/github.com/containerd/containerd/progress/humaans.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// Bytes converts a regular int64 to human readable type.
|
||||
type Bytes int64
|
||||
|
||||
// String returns the string representation of bytes
|
||||
func (b Bytes) String() string {
|
||||
return units.CustomSize("%02.1f %s", float64(b), 1024.0, []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"})
|
||||
}
|
||||
|
||||
// BytesPerSecond is the rate in seconds for byte operations
|
||||
type BytesPerSecond int64
|
||||
|
||||
// NewBytesPerSecond returns the rate that n bytes were written in the provided duration
|
||||
func NewBytesPerSecond(n int64, duration time.Duration) BytesPerSecond {
|
||||
return BytesPerSecond(float64(n) / duration.Seconds())
|
||||
}
|
||||
|
||||
// String returns the string representation of the rate
|
||||
func (bps BytesPerSecond) String() string {
|
||||
return fmt.Sprintf("%v/s", Bytes(bps))
|
||||
}
|
||||
115
vendor/github.com/containerd/containerd/progress/writer.go
generated
vendored
Normal file
115
vendor/github.com/containerd/containerd/progress/writer.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 progress
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/console"
|
||||
)
|
||||
|
||||
var (
|
||||
regexCleanLine = regexp.MustCompile("\x1b\\[[0-9]+m[\x1b]?")
|
||||
)
|
||||
|
||||
// Writer buffers writes until flush, at which time the last screen is cleared
|
||||
// and the current buffer contents are written. This is useful for
|
||||
// implementing progress displays, such as those implemented in docker and
|
||||
// git.
|
||||
type Writer struct {
|
||||
buf bytes.Buffer
|
||||
w io.Writer
|
||||
lines int
|
||||
}
|
||||
|
||||
// NewWriter returns a writer
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
// Write the provided bytes
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
return w.buf.Write(p)
|
||||
}
|
||||
|
||||
// Flush should be called when refreshing the current display.
|
||||
func (w *Writer) Flush() error {
|
||||
if w.buf.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.clearLines(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.lines = countLines(w.buf.String())
|
||||
|
||||
if _, err := w.w.Write(w.buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.buf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(stevvooe): The following are system specific. Break these out if we
|
||||
// decide to build this package further.
|
||||
|
||||
func (w *Writer) clearLines() error {
|
||||
for i := 0; i < w.lines; i++ {
|
||||
if _, err := fmt.Fprintf(w.w, "\x1b[1A\x1b[2K\r"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// countLines in the output. If a line is longer than the console width then
|
||||
// an extra line is added to the count for each wrapped line. If the console
|
||||
// width is undefined then 0 is returned so that no lines are cleared on the next
|
||||
// flush.
|
||||
func countLines(output string) int {
|
||||
con, err := console.ConsoleFromFile(os.Stdin)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
ws, err := con.Size()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
width := int(ws.Width)
|
||||
if width <= 0 {
|
||||
return 0
|
||||
}
|
||||
strlines := strings.Split(output, "\n")
|
||||
lines := -1
|
||||
for _, line := range strlines {
|
||||
lines += (len(stripLine(line))-1)/width + 1
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func stripLine(line string) string {
|
||||
return string(regexCleanLine.ReplaceAll([]byte(line), []byte{}))
|
||||
}
|
||||
5
vendor/github.com/containerd/containerd/remotes/handlers.go
generated
vendored
5
vendor/github.com/containerd/containerd/remotes/handlers.go
generated
vendored
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -182,7 +181,7 @@ func push(ctx context.Context, provider content.Provider, pusher Pusher, desc oc
|
||||
//
|
||||
// Base handlers can be provided which will be called before any push specific
|
||||
// handlers.
|
||||
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, baseHandlers ...images.Handler) error {
|
||||
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, provider content.Provider, platforms []string, baseHandlers ...images.Handler) error {
|
||||
var m sync.Mutex
|
||||
manifestStack := []ocispec.Descriptor{}
|
||||
|
||||
@@ -202,7 +201,7 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, pr
|
||||
pushHandler := PushHandler(pusher, provider)
|
||||
|
||||
handlers := append(baseHandlers,
|
||||
images.FilterPlatform(platforms.Default(), images.ChildrenHandler(provider)),
|
||||
images.FilterPlatforms(images.ChildrenHandler(provider), platforms...),
|
||||
filterHandler,
|
||||
pushHandler,
|
||||
)
|
||||
|
||||
4
vendor/github.com/containerd/containerd/services/content/service.go
generated
vendored
4
vendor/github.com/containerd/containerd/services/content/service.go
generated
vendored
@@ -366,7 +366,7 @@ func (s *service) Write(session api.Content_WriteServer) (err error) {
|
||||
// users use the same writer style for each with a minimum of overhead.
|
||||
if req.Expected != "" {
|
||||
if expected != "" && expected != req.Expected {
|
||||
return status.Errorf(codes.InvalidArgument, "inconsistent digest provided: %v != %v", req.Expected, expected)
|
||||
log.G(ctx).Debugf("commit digest differs from writer digest: %v != %v", req.Expected, expected)
|
||||
}
|
||||
expected = req.Expected
|
||||
|
||||
@@ -383,7 +383,7 @@ func (s *service) Write(session api.Content_WriteServer) (err error) {
|
||||
// Update the expected total. Typically, this could be seen at
|
||||
// negotiation time or on a commit message.
|
||||
if total > 0 && req.Total != total {
|
||||
return status.Errorf(codes.InvalidArgument, "inconsistent total provided: %v != %v", req.Total, total)
|
||||
log.G(ctx).Debugf("commit size differs from writer size: %v != %v", req.Total, total)
|
||||
}
|
||||
total = req.Total
|
||||
}
|
||||
|
||||
203
vendor/github.com/containerd/containerd/snapshots/overlay/overlay.go
generated
vendored
203
vendor/github.com/containerd/containerd/snapshots/overlay/overlay.go
generated
vendored
@@ -44,20 +44,45 @@ func init() {
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
|
||||
ic.Meta.Exports["root"] = ic.Root
|
||||
return NewSnapshotter(ic.Root)
|
||||
return NewSnapshotter(ic.Root, AsynchronousRemove)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SnapshotterConfig is used to configure the overlay snapshotter instance
|
||||
type SnapshotterConfig struct {
|
||||
asyncRemove bool
|
||||
}
|
||||
|
||||
// Opt is an option to configure the overlay snapshotter
|
||||
type Opt func(config *SnapshotterConfig) error
|
||||
|
||||
// AsynchronousRemove defers removal of filesystem content until
|
||||
// the Cleanup method is called. Removals will make the snapshot
|
||||
// referred to by the key unavailable and make the key immediately
|
||||
// available for re-use.
|
||||
func AsynchronousRemove(config *SnapshotterConfig) error {
|
||||
config.asyncRemove = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type snapshotter struct {
|
||||
root string
|
||||
ms *storage.MetaStore
|
||||
root string
|
||||
ms *storage.MetaStore
|
||||
asyncRemove bool
|
||||
}
|
||||
|
||||
// NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs
|
||||
// diffs are stored under the provided root. A metadata file is stored under
|
||||
// the root.
|
||||
func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
||||
func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
|
||||
var config SnapshotterConfig
|
||||
for _, opt := range opts {
|
||||
if err := opt(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -78,8 +103,9 @@ func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
||||
}
|
||||
|
||||
return &snapshotter{
|
||||
root: root,
|
||||
ms: ms,
|
||||
root: root,
|
||||
ms: ms,
|
||||
asyncRemove: config.asyncRemove,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -210,47 +236,50 @@ func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
// Remove abandons the transaction identified by key. All resources
|
||||
// associated with the key will be removed.
|
||||
// Remove abandons the snapshot identified by key. The snapshot will
|
||||
// immediately become unavailable and unrecoverable. Disk space will
|
||||
// be freed up on the next call to `Cleanup`.
|
||||
func (o *snapshotter) Remove(ctx context.Context, key string) (err error) {
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && t != nil {
|
||||
if err != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
id, _, err := storage.Remove(ctx, key)
|
||||
_, _, err = storage.Remove(ctx, key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to remove")
|
||||
}
|
||||
|
||||
path := filepath.Join(o.root, "snapshots", id)
|
||||
renamed := filepath.Join(o.root, "snapshots", "rm-"+id)
|
||||
if err := os.Rename(path, renamed); err != nil {
|
||||
return errors.Wrap(err, "failed to rename")
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
t = nil
|
||||
if err != nil {
|
||||
if err1 := os.Rename(renamed, path); err1 != nil {
|
||||
// May cause inconsistent data on disk
|
||||
log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("failed to rename after failed commit")
|
||||
if !o.asyncRemove {
|
||||
var removals []string
|
||||
removals, err = o.getCleanupDirectories(ctx, t)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get directories for removal")
|
||||
}
|
||||
return errors.Wrap(err, "failed to commit")
|
||||
}
|
||||
if err := os.RemoveAll(renamed); err != nil {
|
||||
// Must be cleaned up, any "rm-*" could be removed if no active transactions
|
||||
log.G(ctx).WithError(err).WithField("path", renamed).Warnf("failed to remove root filesystem")
|
||||
|
||||
// Remove directories after the transaction is closed, failures must not
|
||||
// return error since the transaction is committed with the removal
|
||||
// key no longer available.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
for _, dir := range removals {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
// Walk the committed snapshots.
|
||||
@@ -263,45 +292,94 @@ func (o *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapsho
|
||||
return storage.WalkInfo(ctx, fn)
|
||||
}
|
||||
|
||||
func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
|
||||
var (
|
||||
path string
|
||||
snapshotDir = filepath.Join(o.root, "snapshots")
|
||||
)
|
||||
|
||||
td, err := ioutil.TempDir(snapshotDir, "new-")
|
||||
// Cleanup cleans up disk resources from removed or abandoned snapshots
|
||||
func (o *snapshotter) Cleanup(ctx context.Context) error {
|
||||
cleanup, err := o.cleanupDirectories(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create temp dir")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dir := range cleanup {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *snapshotter) cleanupDirectories(ctx context.Context) ([]string, error) {
|
||||
// Get a write transaction to ensure no other write transaction can be entered
|
||||
// while the cleanup is scanning.
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer t.Rollback()
|
||||
return o.getCleanupDirectories(ctx, t)
|
||||
}
|
||||
|
||||
func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Transactor) ([]string, error) {
|
||||
ids, err := storage.IDMap(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshotDir := filepath.Join(o.root, "snapshots")
|
||||
fd, err := os.Open(snapshotDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
dirs, err := fd.Readdirnames(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanup := []string{}
|
||||
for _, d := range dirs {
|
||||
if _, ok := ids[d]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanup = append(cleanup, filepath.Join(snapshotDir, d))
|
||||
}
|
||||
|
||||
return cleanup, nil
|
||||
}
|
||||
|
||||
func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var td, path string
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if td != "" {
|
||||
if err1 := os.RemoveAll(td); err1 != nil {
|
||||
err = errors.Wrapf(err, "remove failed: %v", err1)
|
||||
log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory")
|
||||
}
|
||||
}
|
||||
if path != "" {
|
||||
if err1 := os.RemoveAll(path); err1 != nil {
|
||||
log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal")
|
||||
err = errors.Wrapf(err, "failed to remove path: %v", err1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fs := filepath.Join(td, "fs")
|
||||
if err = os.MkdirAll(fs, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kind == snapshots.KindActive {
|
||||
if err = os.MkdirAll(filepath.Join(td, "work"), 0711); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||
snapshotDir := filepath.Join(o.root, "snapshots")
|
||||
td, err = o.prepareDirectory(ctx, snapshotDir, kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to create prepare snapshot dir")
|
||||
}
|
||||
rollback := true
|
||||
defer func() {
|
||||
@@ -324,7 +402,11 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||
}
|
||||
|
||||
stat := st.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(fs, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
|
||||
if err := os.Lchown(filepath.Join(td, "fs"), int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
if rerr := t.Rollback(); rerr != nil {
|
||||
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to chown")
|
||||
}
|
||||
}
|
||||
@@ -343,6 +425,25 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||
return o.mounts(s), nil
|
||||
}
|
||||
|
||||
func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) {
|
||||
td, err := ioutil.TempDir(filepath.Join(o.root, "snapshots"), "new-")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to create temp dir")
|
||||
}
|
||||
|
||||
if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil {
|
||||
return td, err
|
||||
}
|
||||
|
||||
if kind == snapshots.KindActive {
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil {
|
||||
return td, err
|
||||
}
|
||||
}
|
||||
|
||||
return td, nil
|
||||
}
|
||||
|
||||
func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount {
|
||||
if len(s.ParentIDs) == 0 {
|
||||
// if we only have one layer/no parents then just return a bind mount as overlay
|
||||
|
||||
20
vendor/github.com/containerd/containerd/snapshots/storage/bolt.go
generated
vendored
20
vendor/github.com/containerd/containerd/snapshots/storage/bolt.go
generated
vendored
@@ -405,6 +405,26 @@ func CommitActive(ctx context.Context, key, name string, usage snapshots.Usage,
|
||||
return fmt.Sprintf("%d", id), nil
|
||||
}
|
||||
|
||||
// IDMap returns all the IDs mapped to their key
|
||||
func IDMap(ctx context.Context) (map[string]string, error) {
|
||||
m := map[string]string{}
|
||||
if err := withBucket(ctx, func(ctx context.Context, bkt, _ *bolt.Bucket) error {
|
||||
return bkt.ForEach(func(k, v []byte) error {
|
||||
// skip non buckets
|
||||
if v != nil {
|
||||
return nil
|
||||
}
|
||||
id := readID(bkt.Bucket(k))
|
||||
m[fmt.Sprintf("%d", id)] = string(k)
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func withSnapshotBucket(ctx context.Context, key string, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
|
||||
tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
|
||||
if !ok {
|
||||
|
||||
8
vendor/github.com/containerd/containerd/vendor.conf
generated
vendored
8
vendor/github.com/containerd/containerd/vendor.conf
generated
vendored
@@ -41,8 +41,9 @@ github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577
|
||||
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
||||
github.com/gotestyourself/gotestyourself 44dbf532bbf5767611f6f2a61bded572e337010a
|
||||
github.com/google/go-cmp v0.1.0
|
||||
|
||||
# cri dependencies
|
||||
github.com/containerd/cri-containerd dcc278739fb31c5369f927c69716275c084c3d53 https://github.com/Random-Liu/cri-containerd.git
|
||||
github.com/containerd/cri 0c876040681ebe8a291fa2cebefdcc2796fa3fc8
|
||||
github.com/blang/semver v3.1.0
|
||||
github.com/containernetworking/cni v0.6.0
|
||||
github.com/containernetworking/plugins v0.6.0
|
||||
@@ -73,3 +74,8 @@ k8s.io/apiserver 8e45eac9dff86447a5c2effe6a3d2cba70121ebf
|
||||
k8s.io/client-go 33bd23f75b6de861994706a322b0afab824b2171
|
||||
k8s.io/kubernetes 05944b1d2ca7f60b09762a330425108f48f6b603
|
||||
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e
|
||||
|
||||
# zfs dependencies
|
||||
github.com/containerd/zfs 2e6f60521b5690bf2f265c416a42b251c2a3ec8e
|
||||
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
||||
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||
|
||||
Reference in New Issue
Block a user