Update containerd to 3c1ef1a714
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
eff311d493
commit
62d1f13217
@ -4,7 +4,7 @@ github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
|
|||||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
||||||
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
||||||
github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e
|
github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e
|
||||||
github.com/containerd/containerd 3013762fc58941e33ba70e8f8d9256911f134124
|
github.com/containerd/containerd 3c1ef1a714cf5b0104f340f76d539802fc24c75f
|
||||||
github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
|
github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
|
||||||
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
|
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
|
||||||
github.com/containerd/go-runc 4f6e87ae043f859a38255247b49c9abc262d002f
|
github.com/containerd/go-runc 4f6e87ae043f859a38255247b49c9abc262d002f
|
||||||
@ -31,7 +31,6 @@ github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
|||||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||||
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
|
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
|
||||||
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
|
||||||
github.com/json-iterator/go 1.0.4
|
github.com/json-iterator/go 1.0.4
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.0
|
github.com/matttproud/golang_protobuf_extensions v1.0.0
|
||||||
github.com/Microsoft/go-winio v0.4.5
|
github.com/Microsoft/go-winio v0.4.5
|
||||||
@ -48,10 +47,8 @@ github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
|
|||||||
github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
|
github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
|
||||||
github.com/prometheus/common 89604d197083d4781071d3c65855d24ecfb0a563
|
github.com/prometheus/common 89604d197083d4781071d3c65855d24ecfb0a563
|
||||||
github.com/prometheus/procfs cb4147076ac75738c9a7d279075a253c0cc5acbd
|
github.com/prometheus/procfs cb4147076ac75738c9a7d279075a253c0cc5acbd
|
||||||
github.com/renstrom/dedent 020d11c3b9c0c7a3c2efcc8e5cf5b9ef7bcea21f
|
|
||||||
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
|
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
|
||||||
github.com/sirupsen/logrus v1.0.0
|
github.com/sirupsen/logrus v1.0.0
|
||||||
github.com/spf13/cobra v0.0.1
|
|
||||||
github.com/spf13/pflag v1.0.0
|
github.com/spf13/pflag v1.0.0
|
||||||
github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577
|
github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577
|
||||||
github.com/stretchr/testify v1.1.4
|
github.com/stretchr/testify v1.1.4
|
||||||
|
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/events"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/platforms"
|
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
"github.com/containerd/containerd/remotes/docker"
|
"github.com/containerd/containerd/remotes/docker"
|
||||||
@ -236,6 +235,10 @@ type RemoteContext struct {
|
|||||||
// If no resolver is provided, defaults to Docker registry resolver.
|
// If no resolver is provided, defaults to Docker registry resolver.
|
||||||
Resolver remotes.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.
|
// 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
|
// If an image is not unpacked on pull, it can be unpacked any time
|
||||||
// afterwards. Unpacking is required to run an image.
|
// 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 {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
|
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher, err := pullCtx.Resolver.Fetcher(ctx, name)
|
fetcher, err := pullCtx.Resolver.Fetcher(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to get fetcher for %q", name)
|
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)
|
childrenHandler := images.ChildrenHandler(store)
|
||||||
// Set any children labels for that content
|
// Set any children labels for that content
|
||||||
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
|
||||||
// Filter the childen by the platform
|
// Filter childen by platforms
|
||||||
childrenHandler = images.FilterPlatform(platforms.Default(), childrenHandler)
|
childrenHandler = images.FilterPlatforms(childrenHandler, pullCtx.Platforms...)
|
||||||
|
|
||||||
handler = images.Handlers(append(pullCtx.BaseHandlers,
|
handler = images.Handlers(append(pullCtx.BaseHandlers,
|
||||||
remotes.FetchHandler(store, fetcher),
|
remotes.FetchHandler(store, fetcher),
|
||||||
@ -371,7 +375,7 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
|
|||||||
return err
|
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
|
// 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
|
// RemoteOpt allows the caller to set distribution options for a remote
|
||||||
type RemoteOpt func(*Client, *RemoteContext) error
|
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
|
// WithPullUnpack is used to unpack an image after pull. This
|
||||||
// uses the snapshotter, content store, and diff service
|
// uses the snapshotter, content store, and diff service
|
||||||
// configured for the client.
|
// 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
|
// 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
|
// This is useful when the digest and size are known beforehand. When
|
||||||
// the size or digest is unknown, these values may be empty.
|
// 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
|
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
|
// seekReader attempts to seek the reader to the given offset, either by
|
||||||
// resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding
|
// resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding
|
||||||
// up to the given offset.
|
// up to the given offset.
|
||||||
|
25
vendor/github.com/containerd/containerd/images/handlers.go
generated
vendored
25
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
|
// FilterPlatforms is a handler wrapper which limits the descriptors returned
|
||||||
// by a handler to a single platform.
|
// by a handler to the specified platforms.
|
||||||
func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
|
func FilterPlatforms(f HandlerFunc, platformList ...string) HandlerFunc {
|
||||||
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||||
children, err := f(ctx, desc)
|
children, err := f(ctx, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -192,7 +192,11 @@ func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var descs []ocispec.Descriptor
|
var descs []ocispec.Descriptor
|
||||||
if platform != "" && isMultiPlatform(desc.MediaType) {
|
|
||||||
|
if len(platformList) == 0 {
|
||||||
|
descs = children
|
||||||
|
} else {
|
||||||
|
for _, platform := range platformList {
|
||||||
p, err := platforms.Parse(platform)
|
p, err := platforms.Parse(platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -204,20 +208,9 @@ func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
|
|||||||
descs = append(descs, d)
|
descs = append(descs, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
descs = children
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return descs, nil
|
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
|
size += desc.Size
|
||||||
return nil, nil
|
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.
|
// 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
|
bucketKeyObjectSnapshots = []byte("snapshots") // stores snapshot references
|
||||||
bucketKeyObjectContent = []byte("content") // stores content references
|
bucketKeyObjectContent = []byte("content") // stores content references
|
||||||
bucketKeyObjectBlob = []byte("blob") // stores content links
|
bucketKeyObjectBlob = []byte("blob") // stores content links
|
||||||
bucketKeyObjectIngest = []byte("ingest") // stores ingest links
|
bucketKeyObjectIngests = []byte("ingests") // stores ingest objects
|
||||||
bucketKeyObjectLeases = []byte("leases") // stores leases
|
bucketKeyObjectLeases = []byte("leases") // stores leases
|
||||||
|
|
||||||
bucketKeyDigest = []byte("digest")
|
bucketKeyDigest = []byte("digest")
|
||||||
@ -70,6 +70,10 @@ var (
|
|||||||
bucketKeyTarget = []byte("target")
|
bucketKeyTarget = []byte("target")
|
||||||
bucketKeyExtensions = []byte("extensions")
|
bucketKeyExtensions = []byte("extensions")
|
||||||
bucketKeyCreatedAt = []byte("createdat")
|
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 {
|
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()))
|
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(dgst.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createIngestBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
|
func getIngestsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
|
||||||
bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngest)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bkt, nil
|
return bkt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIngestBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
|
func getIngestBucket(tx *bolt.Tx, namespace, ref string) *bolt.Bucket {
|
||||||
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngest)
|
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(ref))
|
||||||
}
|
}
|
||||||
|
204
vendor/github.com/containerd/containerd/metadata/content.go
generated
vendored
204
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{}
|
brefs := map[string]string{}
|
||||||
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
if err := view(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||||
bkt := getIngestBucket(tx, ns)
|
bkt := getIngestsBucket(tx, ns)
|
||||||
if bkt == nil {
|
if bkt == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return bkt.ForEach(func(k, v []byte) error {
|
return bkt.ForEach(func(k, v []byte) error {
|
||||||
|
if v == nil {
|
||||||
// TODO(dmcgowan): match name and potentially labels here
|
// TODO(dmcgowan): match name and potentially labels here
|
||||||
brefs[string(k)] = string(v)
|
brefs[string(k)] = string(bkt.Bucket(k).Get(bucketKeyRef))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}); err != 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 {
|
func getRef(tx *bolt.Tx, ns, ref string) string {
|
||||||
bkt := getIngestBucket(tx, ns)
|
bkt := getIngestBucket(tx, ns, ref)
|
||||||
if bkt == nil {
|
if bkt == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
v := bkt.Get([]byte(ref))
|
v := bkt.Get(bucketKeyRef)
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -308,19 +310,29 @@ func (cs *contentStore) Abort(ctx context.Context, ref string) error {
|
|||||||
defer cs.l.RUnlock()
|
defer cs.l.RUnlock()
|
||||||
|
|
||||||
return update(ctx, cs.db, func(tx *bolt.Tx) error {
|
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 {
|
if bkt == nil {
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
||||||
}
|
}
|
||||||
bref := string(bkt.Get([]byte(ref)))
|
bref := string(bkt.Get(bucketKeyRef))
|
||||||
if bref == "" {
|
if bref == "" {
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref)
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if not shared content, delete active ingest on backend
|
||||||
|
if expected == "" {
|
||||||
return cs.Store.Abort(ctx, bref)
|
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 (
|
var (
|
||||||
w content.Writer
|
w content.Writer
|
||||||
exists bool
|
exists bool
|
||||||
|
bref string
|
||||||
)
|
)
|
||||||
if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
|
if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
|
||||||
|
var shared bool
|
||||||
if expected != "" {
|
if expected != "" {
|
||||||
cbkt := getBlobBucket(tx, ns, expected)
|
cbkt := getBlobBucket(tx, ns, expected)
|
||||||
if cbkt != nil {
|
if cbkt != nil {
|
||||||
@ -352,18 +366,24 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
|||||||
exists = true
|
exists = true
|
||||||
return nil
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
brefb := bkt.Get(bucketKeyRef)
|
||||||
bref string
|
|
||||||
brefb = bkt.Get([]byte(ref))
|
|
||||||
)
|
|
||||||
|
|
||||||
if brefb == nil {
|
if brefb == nil {
|
||||||
sid, err := bkt.NextSequence()
|
sid, err := bkt.NextSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -371,21 +391,24 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
|
|||||||
}
|
}
|
||||||
|
|
||||||
bref = createKey(sid, ns, ref)
|
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
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bref = string(brefb)
|
bref = string(brefb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Do not use the passed in expected value here since it was
|
||||||
// already checked against the user metadata. If the content
|
// already checked against the user metadata. The content must
|
||||||
// store has the content, it must still be written before
|
// be committed in the namespace before it will be seen as
|
||||||
// linked into the given namespace. It is possible in the future
|
// available in the current namespace.
|
||||||
// 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, "")
|
w, err = cs.Store.Writer(ctx, bref, size, "")
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
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)
|
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{
|
return &namespacedWriter{
|
||||||
Writer: w,
|
ctx: ctx,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
namespace: ns,
|
namespace: ns,
|
||||||
db: cs.db,
|
db: cs.db,
|
||||||
|
provider: cs.Store,
|
||||||
l: &cs.l,
|
l: &cs.l,
|
||||||
|
w: w,
|
||||||
|
bref: bref,
|
||||||
|
started: time.Now(),
|
||||||
|
expected: expected,
|
||||||
|
size: size,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type namespacedWriter struct {
|
type namespacedWriter struct {
|
||||||
content.Writer
|
ctx context.Context
|
||||||
ref string
|
ref string
|
||||||
namespace string
|
namespace string
|
||||||
db transactor
|
db transactor
|
||||||
|
provider interface {
|
||||||
|
content.Provider
|
||||||
|
content.Ingester
|
||||||
|
}
|
||||||
l *sync.RWMutex
|
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 {
|
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()
|
defer nw.l.RUnlock()
|
||||||
|
|
||||||
return update(ctx, nw.db, func(tx *bolt.Tx) error {
|
return update(ctx, nw.db, func(tx *bolt.Tx) error {
|
||||||
bkt := getIngestBucket(tx, nw.namespace)
|
bkt := getIngestsBucket(tx, nw.namespace)
|
||||||
if bkt != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,7 +542,21 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := nw.Writer.Status()
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -451,10 +564,9 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
|||||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
||||||
}
|
}
|
||||||
size = status.Offset
|
size = status.Offset
|
||||||
|
actual = nw.w.Digest()
|
||||||
|
|
||||||
actual := nw.Writer.Digest()
|
if err := nw.w.Commit(ctx, size, expected); err != nil {
|
||||||
|
|
||||||
if err := nw.Writer.Commit(ctx, size, expected); err != nil {
|
|
||||||
if !errdefs.IsAlreadyExists(err) {
|
if !errdefs.IsAlreadyExists(err) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -462,6 +574,7 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
|||||||
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
|
return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bkt, err := createBlobBucket(tx, nw.namespace, actual)
|
bkt, err := createBlobBucket(tx, nw.namespace, actual)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -484,12 +597,20 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
|||||||
return actual, bkt.Put(bucketKeySize, sizeEncoded)
|
return actual, bkt.Put(bucketKeySize, sizeEncoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nw *namespacedWriter) Status() (content.Status, error) {
|
func (nw *namespacedWriter) Status() (st content.Status, err error) {
|
||||||
st, err := nw.Writer.Status()
|
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 {
|
if err == nil {
|
||||||
st.Ref = nw.ref
|
st.Ref = nw.ref
|
||||||
}
|
}
|
||||||
return st, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *contentStore) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) {
|
func (cs *contentStore) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) {
|
||||||
@ -590,6 +711,7 @@ func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, er
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bbkt := cbkt.Bucket(bucketKeyObjectBlob)
|
bbkt := cbkt.Bucket(bucketKeyObjectBlob)
|
||||||
|
if bbkt != nil {
|
||||||
if err := bbkt.ForEach(func(ck, cv []byte) error {
|
if err := bbkt.ForEach(func(ck, cv []byte) error {
|
||||||
if cv == nil {
|
if cv == nil {
|
||||||
seen[string(ck)] = struct{}{}
|
seen[string(ck)] = struct{}{}
|
||||||
@ -600,6 +722,22 @@ func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return 0, err
|
return 0, 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
|
// dbVersion represents updates to the schema
|
||||||
// version which are additions and compatible with
|
// version which are additions and compatible with
|
||||||
// prior version of the same schema.
|
// prior version of the same schema.
|
||||||
dbVersion = 1
|
dbVersion = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB represents a metadata database backed by a bolt
|
// 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,
|
version: 1,
|
||||||
migrate: addChildLinks,
|
migrate: addChildLinks,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
schema: "v1",
|
||||||
|
version: 2,
|
||||||
|
migrate: migrateIngests,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// addChildLinks Adds children key to the snapshotters to enforce snapshot
|
// addChildLinks Adds children key to the snapshotters to enforce snapshot
|
||||||
@ -99,3 +104,53 @@ func addChildLinks(tx *bolt.Tx) error {
|
|||||||
|
|
||||||
return nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cleaner interface {
|
||||||
|
Cleanup(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *snapshotter) garbageCollect(ctx context.Context) (d time.Duration, err error) {
|
func (s *snapshotter) garbageCollect(ctx context.Context) (d time.Duration, err error) {
|
||||||
s.l.Lock()
|
s.l.Lock()
|
||||||
t1 := time.Now()
|
t1 := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
s.l.Unlock()
|
||||||
|
if err == nil {
|
||||||
|
if c, ok := s.Snapshotter.(cleaner); ok {
|
||||||
|
err = c.Cleanup(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d = time.Now().Sub(t1)
|
d = time.Now().Sub(t1)
|
||||||
}
|
}
|
||||||
s.l.Unlock()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
seen := map[string]struct{}{}
|
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
|
package namespaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/platforms"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"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
|
// Base handlers can be provided which will be called before any push specific
|
||||||
// handlers.
|
// 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
|
var m sync.Mutex
|
||||||
manifestStack := []ocispec.Descriptor{}
|
manifestStack := []ocispec.Descriptor{}
|
||||||
|
|
||||||
@ -202,7 +201,7 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, pr
|
|||||||
pushHandler := PushHandler(pusher, provider)
|
pushHandler := PushHandler(pusher, provider)
|
||||||
|
|
||||||
handlers := append(baseHandlers,
|
handlers := append(baseHandlers,
|
||||||
images.FilterPlatform(platforms.Default(), images.ChildrenHandler(provider)),
|
images.FilterPlatforms(images.ChildrenHandler(provider), platforms...),
|
||||||
filterHandler,
|
filterHandler,
|
||||||
pushHandler,
|
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.
|
// users use the same writer style for each with a minimum of overhead.
|
||||||
if req.Expected != "" {
|
if req.Expected != "" {
|
||||||
if expected != "" && expected != 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
|
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
|
// Update the expected total. Typically, this could be seen at
|
||||||
// negotiation time or on a commit message.
|
// negotiation time or on a commit message.
|
||||||
if total > 0 && req.Total != total {
|
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
|
total = req.Total
|
||||||
}
|
}
|
||||||
|
195
vendor/github.com/containerd/containerd/snapshots/overlay/overlay.go
generated
vendored
195
vendor/github.com/containerd/containerd/snapshots/overlay/overlay.go
generated
vendored
@ -44,20 +44,45 @@ func init() {
|
|||||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||||
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
|
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
|
||||||
ic.Meta.Exports["root"] = ic.Root
|
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 {
|
type snapshotter struct {
|
||||||
root string
|
root string
|
||||||
ms *storage.MetaStore
|
ms *storage.MetaStore
|
||||||
|
asyncRemove bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs
|
// NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs
|
||||||
// diffs are stored under the provided root. A metadata file is stored under
|
// diffs are stored under the provided root. A metadata file is stored under
|
||||||
// the root.
|
// 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 {
|
if err := os.MkdirAll(root, 0700); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -80,6 +105,7 @@ func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
|||||||
return &snapshotter{
|
return &snapshotter{
|
||||||
root: root,
|
root: root,
|
||||||
ms: ms,
|
ms: ms,
|
||||||
|
asyncRemove: config.asyncRemove,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,47 +236,50 @@ func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
|||||||
return t.Commit()
|
return t.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove abandons the transaction identified by key. All resources
|
// Remove abandons the snapshot identified by key. The snapshot will
|
||||||
// associated with the key will be removed.
|
// 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) {
|
func (o *snapshotter) Remove(ctx context.Context, key string) (err error) {
|
||||||
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
ctx, t, err := o.ms.TransactionContext(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && t != nil {
|
if err != nil {
|
||||||
if rerr := t.Rollback(); rerr != nil {
|
if rerr := t.Rollback(); rerr != nil {
|
||||||
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
|
log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
id, _, err := storage.Remove(ctx, key)
|
_, _, err = storage.Remove(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to remove")
|
return errors.Wrap(err, "failed to remove")
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(o.root, "snapshots", id)
|
if !o.asyncRemove {
|
||||||
renamed := filepath.Join(o.root, "snapshots", "rm-"+id)
|
var removals []string
|
||||||
if err := os.Rename(path, renamed); err != nil {
|
removals, err = o.getCleanupDirectories(ctx, t)
|
||||||
return errors.Wrap(err, "failed to rename")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.Commit()
|
|
||||||
t = nil
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err1 := os.Rename(renamed, path); err1 != nil {
|
return errors.Wrap(err, "unable to get directories for removal")
|
||||||
// May cause inconsistent data on disk
|
|
||||||
log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("failed to rename after failed commit")
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// 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 t.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the committed snapshots.
|
// 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)
|
return storage.WalkInfo(ctx, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
|
// Cleanup cleans up disk resources from removed or abandoned snapshots
|
||||||
var (
|
func (o *snapshotter) Cleanup(ctx context.Context) error {
|
||||||
path string
|
cleanup, err := o.cleanupDirectories(ctx)
|
||||||
snapshotDir = filepath.Join(o.root, "snapshots")
|
|
||||||
)
|
|
||||||
|
|
||||||
td, err := ioutil.TempDir(snapshotDir, "new-")
|
|
||||||
if err != nil {
|
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() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if td != "" {
|
if td != "" {
|
||||||
if err1 := os.RemoveAll(td); err1 != nil {
|
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 path != "" {
|
||||||
if err1 := os.RemoveAll(path); err1 != nil {
|
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)
|
err = errors.Wrapf(err, "failed to remove path: %v", err1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fs := filepath.Join(td, "fs")
|
snapshotDir := filepath.Join(o.root, "snapshots")
|
||||||
if err = os.MkdirAll(fs, 0755); err != nil {
|
td, err = o.prepareDirectory(ctx, snapshotDir, kind)
|
||||||
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)
|
|
||||||
if err != nil {
|
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
|
rollback := true
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -324,7 +402,11 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
|||||||
}
|
}
|
||||||
|
|
||||||
stat := st.Sys().(*syscall.Stat_t)
|
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")
|
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
|
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 {
|
func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount {
|
||||||
if len(s.ParentIDs) == 0 {
|
if len(s.ParentIDs) == 0 {
|
||||||
// if we only have one layer/no parents then just return a bind mount as overlay
|
// 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
|
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 {
|
func withSnapshotBucket(ctx context.Context, key string, fn func(context.Context, *bolt.Bucket, *bolt.Bucket) error) error {
|
||||||
tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
|
tx, ok := ctx.Value(transactionKey{}).(*bolt.Tx)
|
||||||
if !ok {
|
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/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
||||||
github.com/gotestyourself/gotestyourself 44dbf532bbf5767611f6f2a61bded572e337010a
|
github.com/gotestyourself/gotestyourself 44dbf532bbf5767611f6f2a61bded572e337010a
|
||||||
github.com/google/go-cmp v0.1.0
|
github.com/google/go-cmp v0.1.0
|
||||||
|
|
||||||
# cri dependencies
|
# 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/blang/semver v3.1.0
|
||||||
github.com/containernetworking/cni v0.6.0
|
github.com/containernetworking/cni v0.6.0
|
||||||
github.com/containernetworking/plugins 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/client-go 33bd23f75b6de861994706a322b0afab824b2171
|
||||||
k8s.io/kubernetes 05944b1d2ca7f60b09762a330425108f48f6b603
|
k8s.io/kubernetes 05944b1d2ca7f60b09762a330425108f48f6b603
|
||||||
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e
|
k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e
|
||||||
|
|
||||||
|
# zfs dependencies
|
||||||
|
github.com/containerd/zfs 2e6f60521b5690bf2f265c416a42b251c2a3ec8e
|
||||||
|
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
||||||
|
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||||
|
13
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
13
vendor/github.com/inconshreveable/mousetrap/LICENSE
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
Copyright 2014 Alan Shreve
|
|
||||||
|
|
||||||
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.
|
|
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
23
vendor/github.com/inconshreveable/mousetrap/README.md
generated
vendored
@ -1,23 +0,0 @@
|
|||||||
# mousetrap
|
|
||||||
|
|
||||||
mousetrap is a tiny library that answers a single question.
|
|
||||||
|
|
||||||
On a Windows machine, was the process invoked by someone double clicking on
|
|
||||||
the executable file while browsing in explorer?
|
|
||||||
|
|
||||||
### Motivation
|
|
||||||
|
|
||||||
Windows developers unfamiliar with command line tools will often "double-click"
|
|
||||||
the executable for a tool. Because most CLI tools print the help and then exit
|
|
||||||
when invoked without arguments, this is often very frustrating for those users.
|
|
||||||
|
|
||||||
mousetrap provides a way to detect these invocations so that you can provide
|
|
||||||
more helpful behavior and instructions on how to run the CLI tool. To see what
|
|
||||||
this looks like, both from an organizational and a technical perspective, see
|
|
||||||
https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
|
|
||||||
|
|
||||||
### The interface
|
|
||||||
|
|
||||||
The library exposes a single interface:
|
|
||||||
|
|
||||||
func StartedByExplorer() (bool)
|
|
15
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
15
vendor/github.com/inconshreveable/mousetrap/trap_others.go
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package mousetrap
|
|
||||||
|
|
||||||
// StartedByExplorer returns true if the program was invoked by the user
|
|
||||||
// double-clicking on the executable from explorer.exe
|
|
||||||
//
|
|
||||||
// It is conservative and returns false if any of the internal calls fail.
|
|
||||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
|
||||||
// whether it was launched from explorer.exe
|
|
||||||
//
|
|
||||||
// On non-Windows platforms, it always returns false.
|
|
||||||
func StartedByExplorer() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
98
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
98
vendor/github.com/inconshreveable/mousetrap/trap_windows.go
generated
vendored
@ -1,98 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
// +build !go1.4
|
|
||||||
|
|
||||||
package mousetrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// defined by the Win32 API
|
|
||||||
th32cs_snapprocess uintptr = 0x2
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel = syscall.MustLoadDLL("kernel32.dll")
|
|
||||||
CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot")
|
|
||||||
Process32First = kernel.MustFindProc("Process32FirstW")
|
|
||||||
Process32Next = kernel.MustFindProc("Process32NextW")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProcessEntry32 structure defined by the Win32 API
|
|
||||||
type processEntry32 struct {
|
|
||||||
dwSize uint32
|
|
||||||
cntUsage uint32
|
|
||||||
th32ProcessID uint32
|
|
||||||
th32DefaultHeapID int
|
|
||||||
th32ModuleID uint32
|
|
||||||
cntThreads uint32
|
|
||||||
th32ParentProcessID uint32
|
|
||||||
pcPriClassBase int32
|
|
||||||
dwFlags uint32
|
|
||||||
szExeFile [syscall.MAX_PATH]uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProcessEntry(pid int) (pe *processEntry32, err error) {
|
|
||||||
snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0))
|
|
||||||
if snapshot == uintptr(syscall.InvalidHandle) {
|
|
||||||
err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer syscall.CloseHandle(syscall.Handle(snapshot))
|
|
||||||
|
|
||||||
var processEntry processEntry32
|
|
||||||
processEntry.dwSize = uint32(unsafe.Sizeof(processEntry))
|
|
||||||
ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
|
|
||||||
if ok == 0 {
|
|
||||||
err = fmt.Errorf("Process32First: %v", e1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if processEntry.th32ProcessID == uint32(pid) {
|
|
||||||
pe = &processEntry
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
|
|
||||||
if ok == 0 {
|
|
||||||
err = fmt.Errorf("Process32Next: %v", e1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getppid() (pid int, err error) {
|
|
||||||
pe, err := getProcessEntry(os.Getpid())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = int(pe.th32ParentProcessID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
|
||||||
// on the executable from explorer.exe
|
|
||||||
//
|
|
||||||
// It is conservative and returns false if any of the internal calls fail.
|
|
||||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
|
||||||
// whether it was launched from explorer.exe
|
|
||||||
func StartedByExplorer() bool {
|
|
||||||
ppid, err := getppid()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
pe, err := getProcessEntry(ppid)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
name := syscall.UTF16ToString(pe.szExeFile[:])
|
|
||||||
return name == "explorer.exe"
|
|
||||||
}
|
|
46
vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go
generated
vendored
46
vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go
generated
vendored
@ -1,46 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
// +build go1.4
|
|
||||||
|
|
||||||
package mousetrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
|
|
||||||
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer syscall.CloseHandle(snapshot)
|
|
||||||
var procEntry syscall.ProcessEntry32
|
|
||||||
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
|
|
||||||
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if procEntry.ProcessID == uint32(pid) {
|
|
||||||
return &procEntry, nil
|
|
||||||
}
|
|
||||||
err = syscall.Process32Next(snapshot, &procEntry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartedByExplorer returns true if the program was invoked by the user double-clicking
|
|
||||||
// on the executable from explorer.exe
|
|
||||||
//
|
|
||||||
// It is conservative and returns false if any of the internal calls fail.
|
|
||||||
// It does not guarantee that the program was run from a terminal. It only can tell you
|
|
||||||
// whether it was launched from explorer.exe
|
|
||||||
func StartedByExplorer() bool {
|
|
||||||
pe, err := getProcessEntry(os.Getppid())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
|
|
||||||
}
|
|
21
vendor/github.com/renstrom/dedent/LICENSE
generated
vendored
21
vendor/github.com/renstrom/dedent/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Peter Renström
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
50
vendor/github.com/renstrom/dedent/README.md
generated
vendored
50
vendor/github.com/renstrom/dedent/README.md
generated
vendored
@ -1,50 +0,0 @@
|
|||||||
# Dedent
|
|
||||||
|
|
||||||
[](https://travis-ci.org/renstrom/dedent)
|
|
||||||
[](https://godoc.org/github.com/renstrom/dedent)
|
|
||||||
|
|
||||||
Removes common leading whitespace from multiline strings. Inspired by [`textwrap.dedent`](https://docs.python.org/3/library/textwrap.html#textwrap.dedent) in Python.
|
|
||||||
|
|
||||||
## Usage / example
|
|
||||||
|
|
||||||
Imagine the following snippet that prints a multiline string. You want the indentation to both look nice in the code as well as in the actual output.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/renstrom/dedent"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
s := `Lorem ipsum dolor sit amet,
|
|
||||||
consectetur adipiscing elit.
|
|
||||||
Curabitur justo tellus, facilisis nec efficitur dictum,
|
|
||||||
fermentum vitae ligula. Sed eu convallis sapien.`
|
|
||||||
fmt.Println(dedent.Dedent(s))
|
|
||||||
fmt.Println("-------------")
|
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To illustrate the difference, here's the output:
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go run main.go
|
|
||||||
Lorem ipsum dolor sit amet,
|
|
||||||
consectetur adipiscing elit.
|
|
||||||
Curabitur justo tellus, facilisis nec efficitur dictum,
|
|
||||||
fermentum vitae ligula. Sed eu convallis sapien.
|
|
||||||
-------------
|
|
||||||
Lorem ipsum dolor sit amet,
|
|
||||||
consectetur adipiscing elit.
|
|
||||||
Curabitur justo tellus, facilisis nec efficitur dictum,
|
|
||||||
fermentum vitae ligula. Sed eu convallis sapien.
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
49
vendor/github.com/renstrom/dedent/dedent.go
generated
vendored
49
vendor/github.com/renstrom/dedent/dedent.go
generated
vendored
@ -1,49 +0,0 @@
|
|||||||
package dedent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$")
|
|
||||||
leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dedent removes any common leading whitespace from every line in text.
|
|
||||||
//
|
|
||||||
// This can be used to make multiline strings to line up with the left edge of
|
|
||||||
// the display, while still presenting them in the source code in indented
|
|
||||||
// form.
|
|
||||||
func Dedent(text string) string {
|
|
||||||
var margin string
|
|
||||||
|
|
||||||
text = whitespaceOnly.ReplaceAllString(text, "")
|
|
||||||
indents := leadingWhitespace.FindAllStringSubmatch(text, -1)
|
|
||||||
|
|
||||||
// Look for the longest leading string of spaces and tabs common to all
|
|
||||||
// lines.
|
|
||||||
for i, indent := range indents {
|
|
||||||
if i == 0 {
|
|
||||||
margin = indent[1]
|
|
||||||
} else if strings.HasPrefix(indent[1], margin) {
|
|
||||||
// Current line more deeply indented than previous winner:
|
|
||||||
// no change (previous winner is still on top).
|
|
||||||
continue
|
|
||||||
} else if strings.HasPrefix(margin, indent[1]) {
|
|
||||||
// Current line consistent with and no deeper than previous winner:
|
|
||||||
// it's the new winner.
|
|
||||||
margin = indent[1]
|
|
||||||
} else {
|
|
||||||
// Current line and previous winner have no common whitespace:
|
|
||||||
// there is no margin.
|
|
||||||
margin = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if margin != "" {
|
|
||||||
text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "")
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
174
vendor/github.com/spf13/cobra/LICENSE.txt
generated
vendored
174
vendor/github.com/spf13/cobra/LICENSE.txt
generated
vendored
@ -1,174 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
721
vendor/github.com/spf13/cobra/README.md
generated
vendored
721
vendor/github.com/spf13/cobra/README.md
generated
vendored
@ -1,721 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
|
|
||||||
|
|
||||||
Many of the most widely used Go projects are built using Cobra including:
|
|
||||||
|
|
||||||
* [Kubernetes](http://kubernetes.io/)
|
|
||||||
* [Hugo](http://gohugo.io)
|
|
||||||
* [rkt](https://github.com/coreos/rkt)
|
|
||||||
* [etcd](https://github.com/coreos/etcd)
|
|
||||||
* [Moby (former Docker)](https://github.com/moby/moby)
|
|
||||||
* [Docker (distribution)](https://github.com/docker/distribution)
|
|
||||||
* [OpenShift](https://www.openshift.com/)
|
|
||||||
* [Delve](https://github.com/derekparker/delve)
|
|
||||||
* [GopherJS](http://www.gopherjs.org/)
|
|
||||||
* [CockroachDB](http://www.cockroachlabs.com/)
|
|
||||||
* [Bleve](http://www.blevesearch.com/)
|
|
||||||
* [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
|
|
||||||
* [GiantSwarm's swarm](https://github.com/giantswarm/cli)
|
|
||||||
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
|
||||||
* [rclone](http://rclone.org/)
|
|
||||||
* [nehm](https://github.com/bogem/nehm)
|
|
||||||
|
|
||||||
[](https://travis-ci.org/spf13/cobra)
|
|
||||||
[](https://circleci.com/gh/spf13/cobra)
|
|
||||||
[](https://godoc.org/github.com/spf13/cobra)
|
|
||||||
|
|
||||||
# Table of Contents
|
|
||||||
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Concepts](#concepts)
|
|
||||||
* [Commands](#commands)
|
|
||||||
* [Flags](#flags)
|
|
||||||
- [Installing](#installing)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
* [Using the Cobra Generator](#using-the-cobra-generator)
|
|
||||||
* [Using the Cobra Library](#using-the-cobra-library)
|
|
||||||
* [Working with Flags](#working-with-flags)
|
|
||||||
* [Positional and Custom Arguments](#positional-and-custom-arguments)
|
|
||||||
* [Example](#example)
|
|
||||||
* [Help Command](#help-command)
|
|
||||||
* [Usage Message](#usage-message)
|
|
||||||
* [PreRun and PostRun Hooks](#prerun-and-postrun-hooks)
|
|
||||||
* [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens)
|
|
||||||
* [Generating documentation for your command](#generating-documentation-for-your-command)
|
|
||||||
* [Generating bash completions](#generating-bash-completions)
|
|
||||||
- [Contributing](#contributing)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
# Overview
|
|
||||||
|
|
||||||
Cobra is a library providing a simple interface to create powerful modern CLI
|
|
||||||
interfaces similar to git & go tools.
|
|
||||||
|
|
||||||
Cobra is also an application that will generate your application scaffolding to rapidly
|
|
||||||
develop a Cobra-based application.
|
|
||||||
|
|
||||||
Cobra provides:
|
|
||||||
* Easy subcommand-based CLIs: `app server`, `app fetch`, etc.
|
|
||||||
* Fully POSIX-compliant flags (including short & long versions)
|
|
||||||
* Nested subcommands
|
|
||||||
* Global, local and cascading flags
|
|
||||||
* Easy generation of applications & commands with `cobra init appname` & `cobra add cmdname`
|
|
||||||
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
|
||||||
* Automatic help generation for commands and flags
|
|
||||||
* Automatic help flag recognition of `-h`, `--help`, etc.
|
|
||||||
* Automatically generated bash autocomplete for your application
|
|
||||||
* Automatically generated man pages for your application
|
|
||||||
* Command aliases so you can change things without breaking them
|
|
||||||
* The flexibility to define your own help, usage, etc.
|
|
||||||
* Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps
|
|
||||||
|
|
||||||
# Concepts
|
|
||||||
|
|
||||||
Cobra is built on a structure of commands, arguments & flags.
|
|
||||||
|
|
||||||
**Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions.
|
|
||||||
|
|
||||||
The best applications will read like sentences when used. Users will know how
|
|
||||||
to use the application because they will natively understand how to use it.
|
|
||||||
|
|
||||||
The pattern to follow is
|
|
||||||
`APPNAME VERB NOUN --ADJECTIVE.`
|
|
||||||
or
|
|
||||||
`APPNAME COMMAND ARG --FLAG`
|
|
||||||
|
|
||||||
A few good real world examples may better illustrate this point.
|
|
||||||
|
|
||||||
In the following example, 'server' is a command, and 'port' is a flag:
|
|
||||||
|
|
||||||
hugo server --port=1313
|
|
||||||
|
|
||||||
In this command we are telling Git to clone the url bare.
|
|
||||||
|
|
||||||
git clone URL --bare
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
Command is the central point of the application. Each interaction that
|
|
||||||
the application supports will be contained in a Command. A command can
|
|
||||||
have children commands and optionally run an action.
|
|
||||||
|
|
||||||
In the example above, 'server' is the command.
|
|
||||||
|
|
||||||
[More about cobra.Command](https://godoc.org/github.com/spf13/cobra#Command)
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
A flag is a way to modify the behavior of a command. Cobra supports
|
|
||||||
fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/).
|
|
||||||
A Cobra command can define flags that persist through to children commands
|
|
||||||
and flags that are only available to that command.
|
|
||||||
|
|
||||||
In the example above, 'port' is the flag.
|
|
||||||
|
|
||||||
Flag functionality is provided by the [pflag
|
|
||||||
library](https://github.com/spf13/pflag), a fork of the flag standard library
|
|
||||||
which maintains the same interface while adding POSIX compliance.
|
|
||||||
|
|
||||||
# Installing
|
|
||||||
Using Cobra is easy. First, use `go get` to install the latest version
|
|
||||||
of the library. This command will install the `cobra` generator executable
|
|
||||||
along with the library and its dependencies:
|
|
||||||
|
|
||||||
go get -u github.com/spf13/cobra/cobra
|
|
||||||
|
|
||||||
Next, include Cobra in your application:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/spf13/cobra"
|
|
||||||
```
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
While you are welcome to provide your own organization, typically a Cobra-based
|
|
||||||
application will follow the following organizational structure:
|
|
||||||
|
|
||||||
```
|
|
||||||
▾ appName/
|
|
||||||
▾ cmd/
|
|
||||||
add.go
|
|
||||||
your.go
|
|
||||||
commands.go
|
|
||||||
here.go
|
|
||||||
main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
In a Cobra app, typically the main.go file is very bare. It serves one purpose: initializing Cobra.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"{pathToYourApp}/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := cmd.RootCmd.Execute(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using the Cobra Generator
|
|
||||||
|
|
||||||
Cobra provides its own program that will create your application and add any
|
|
||||||
commands you want. It's the easiest way to incorporate Cobra into your application.
|
|
||||||
|
|
||||||
[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it.
|
|
||||||
|
|
||||||
## Using the Cobra Library
|
|
||||||
|
|
||||||
To manually implement Cobra you need to create a bare main.go file and a RootCmd file.
|
|
||||||
You will optionally provide additional commands as you see fit.
|
|
||||||
|
|
||||||
### Create rootCmd
|
|
||||||
|
|
||||||
Cobra doesn't require any special constructors. Simply create your commands.
|
|
||||||
|
|
||||||
Ideally you place this in app/cmd/root.go:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var RootCmd = &cobra.Command{
|
|
||||||
Use: "hugo",
|
|
||||||
Short: "Hugo is a very fast static site generator",
|
|
||||||
Long: `A Fast and Flexible Static Site Generator built with
|
|
||||||
love by spf13 and friends in Go.
|
|
||||||
Complete documentation is available at http://hugo.spf13.com`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
// Do Stuff Here
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You will additionally define flags and handle configuration in your init() function.
|
|
||||||
|
|
||||||
For example cmd/root.go:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
|
|
||||||
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
|
|
||||||
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
|
|
||||||
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
|
|
||||||
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
|
|
||||||
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
|
||||||
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase"))
|
|
||||||
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper"))
|
|
||||||
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
|
|
||||||
viper.SetDefault("license", "apache")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Execute() {
|
|
||||||
RootCmd.Execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initConfig() {
|
|
||||||
// Don't forget to read config either from cfgFile or from home directory!
|
|
||||||
if cfgFile != "" {
|
|
||||||
// Use config file from the flag.
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
// Find home directory.
|
|
||||||
home, err := homedir.Dir()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search config in home directory with name ".cobra" (without extension).
|
|
||||||
viper.AddConfigPath(home)
|
|
||||||
viper.SetConfigName(".cobra")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
fmt.Println("Can't read config:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create your main.go
|
|
||||||
|
|
||||||
With the root command you need to have your main function execute it.
|
|
||||||
Execute should be run on the root for clarity, though it can be called on any command.
|
|
||||||
|
|
||||||
In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"{pathToYourApp}/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := cmd.RootCmd.Execute(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create additional commands
|
|
||||||
|
|
||||||
Additional commands can be defined and typically are each given their own file
|
|
||||||
inside of the cmd/ directory.
|
|
||||||
|
|
||||||
If you wanted to create a version command you would create cmd/version.go and
|
|
||||||
populate it with the following:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(versionCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
|
||||||
Use: "version",
|
|
||||||
Short: "Print the version number of Hugo",
|
|
||||||
Long: `All software has versions. This is Hugo's`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Working with Flags
|
|
||||||
|
|
||||||
Flags provide modifiers to control how the action command operates.
|
|
||||||
|
|
||||||
### Assign flags to a command
|
|
||||||
|
|
||||||
Since the flags are defined and used in different locations, we need to
|
|
||||||
define a variable outside with the correct scope to assign the flag to
|
|
||||||
work with.
|
|
||||||
|
|
||||||
```go
|
|
||||||
var Verbose bool
|
|
||||||
var Source string
|
|
||||||
```
|
|
||||||
|
|
||||||
There are two different approaches to assign a flag.
|
|
||||||
|
|
||||||
### Persistent Flags
|
|
||||||
|
|
||||||
A flag can be 'persistent' meaning that this flag will be available to the
|
|
||||||
command it's assigned to as well as every command under that command. For
|
|
||||||
global flags, assign a flag as a persistent flag on the root.
|
|
||||||
|
|
||||||
```go
|
|
||||||
RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Flags
|
|
||||||
|
|
||||||
A flag can also be assigned locally which will only apply to that specific command.
|
|
||||||
|
|
||||||
```go
|
|
||||||
RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Flag on Parent Commands
|
|
||||||
|
|
||||||
By default Cobra only parses local flags on the target command, any local flags on
|
|
||||||
parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will
|
|
||||||
parse local flags on each command before executing the target command.
|
|
||||||
|
|
||||||
```go
|
|
||||||
command := cobra.Command{
|
|
||||||
Use: "print [OPTIONS] [COMMANDS]",
|
|
||||||
TraverseChildren: true,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bind Flags with Config
|
|
||||||
|
|
||||||
You can also bind your flags with [viper](https://github.com/spf13/viper):
|
|
||||||
```go
|
|
||||||
var author string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
|
|
||||||
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example the persistent flag `author` is bound with `viper`.
|
|
||||||
**Note**, that the variable `author` will not be set to the value from config,
|
|
||||||
when the `--author` flag is not provided by user.
|
|
||||||
|
|
||||||
More in [viper documentation](https://github.com/spf13/viper#working-with-flags).
|
|
||||||
|
|
||||||
## Positional and Custom Arguments
|
|
||||||
|
|
||||||
Validation of positional arguments can be specified using the `Args` field
|
|
||||||
of `Command`.
|
|
||||||
|
|
||||||
The following validators are built in:
|
|
||||||
|
|
||||||
- `NoArgs` - the command will report an error if there are any positional args.
|
|
||||||
- `ArbitraryArgs` - the command will accept any args.
|
|
||||||
- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`.
|
|
||||||
- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args.
|
|
||||||
- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args.
|
|
||||||
- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args.
|
|
||||||
- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args.
|
|
||||||
|
|
||||||
An example of setting the custom validator:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var cmd = &cobra.Command{
|
|
||||||
Short: "hello",
|
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return errors.New("requires at least one arg")
|
|
||||||
}
|
|
||||||
if myapp.IsValidColor(args[0]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("invalid color specified: %s", args[0])
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("Hello, World!")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
In the example below, we have defined three commands. Two are at the top level
|
|
||||||
and one (cmdTimes) is a child of one of the top commands. In this case the root
|
|
||||||
is not executable meaning that a subcommand is required. This is accomplished
|
|
||||||
by not providing a 'Run' for the 'rootCmd'.
|
|
||||||
|
|
||||||
We have only defined one flag for a single command.
|
|
||||||
|
|
||||||
More documentation about flags is available at https://github.com/spf13/pflag
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var echoTimes int
|
|
||||||
|
|
||||||
var cmdPrint = &cobra.Command{
|
|
||||||
Use: "print [string to print]",
|
|
||||||
Short: "Print anything to the screen",
|
|
||||||
Long: `print is for printing anything back to the screen.
|
|
||||||
For many years people have printed back to the screen.`,
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("Print: " + strings.Join(args, " "))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdEcho = &cobra.Command{
|
|
||||||
Use: "echo [string to echo]",
|
|
||||||
Short: "Echo anything to the screen",
|
|
||||||
Long: `echo is for echoing anything back.
|
|
||||||
Echo works a lot like print, except it has a child command.`,
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("Print: " + strings.Join(args, " "))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdTimes = &cobra.Command{
|
|
||||||
Use: "times [# times] [string to echo]",
|
|
||||||
Short: "Echo anything to the screen more times",
|
|
||||||
Long: `echo things multiple times back to the user by providing
|
|
||||||
a count and a string.`,
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
for i := 0; i < echoTimes; i++ {
|
|
||||||
fmt.Println("Echo: " + strings.Join(args, " "))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{Use: "app"}
|
|
||||||
rootCmd.AddCommand(cmdPrint, cmdEcho)
|
|
||||||
cmdEcho.AddCommand(cmdTimes)
|
|
||||||
rootCmd.Execute()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/).
|
|
||||||
|
|
||||||
## Help Command
|
|
||||||
|
|
||||||
Cobra automatically adds a help command to your application when you have subcommands.
|
|
||||||
This will be called when a user runs 'app help'. Additionally, help will also
|
|
||||||
support all other commands as input. Say, for instance, you have a command called
|
|
||||||
'create' without any additional configuration; Cobra will work when 'app help
|
|
||||||
create' is called. Every command will automatically have the '--help' flag added.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
The following output is automatically generated by Cobra. Nothing beyond the
|
|
||||||
command and flag definitions are needed.
|
|
||||||
|
|
||||||
$ cobra help
|
|
||||||
|
|
||||||
Cobra is a CLI library for Go that empowers applications.
|
|
||||||
This application is a tool to generate the needed files
|
|
||||||
to quickly create a Cobra application.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
cobra [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
add Add a command to a Cobra Application
|
|
||||||
help Help about any command
|
|
||||||
init Initialize a Cobra Application
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
|
||||||
--config string config file (default is $HOME/.cobra.yaml)
|
|
||||||
-h, --help help for cobra
|
|
||||||
-l, --license string name of license for the project
|
|
||||||
--viper use Viper for configuration (default true)
|
|
||||||
|
|
||||||
Use "cobra [command] --help" for more information about a command.
|
|
||||||
|
|
||||||
|
|
||||||
Help is just a command like any other. There is no special logic or behavior
|
|
||||||
around it. In fact, you can provide your own if you want.
|
|
||||||
|
|
||||||
### Defining your own help
|
|
||||||
|
|
||||||
You can provide your own Help command or your own template for the default command to use
|
|
||||||
with followind functions:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cmd.SetHelpCommand(cmd *Command)
|
|
||||||
cmd.SetHelpFunc(f func(*Command, []string))
|
|
||||||
cmd.SetHelpTemplate(s string)
|
|
||||||
```
|
|
||||||
|
|
||||||
The latter two will also apply to any children commands.
|
|
||||||
|
|
||||||
## Usage Message
|
|
||||||
|
|
||||||
When the user provides an invalid flag or invalid command, Cobra responds by
|
|
||||||
showing the user the 'usage'.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
You may recognize this from the help above. That's because the default help
|
|
||||||
embeds the usage as part of its output.
|
|
||||||
|
|
||||||
$ cobra --invalid
|
|
||||||
Error: unknown flag: --invalid
|
|
||||||
Usage:
|
|
||||||
cobra [command]
|
|
||||||
|
|
||||||
Available Commands:
|
|
||||||
add Add a command to a Cobra Application
|
|
||||||
help Help about any command
|
|
||||||
init Initialize a Cobra Application
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-a, --author string author name for copyright attribution (default "YOUR NAME")
|
|
||||||
--config string config file (default is $HOME/.cobra.yaml)
|
|
||||||
-h, --help help for cobra
|
|
||||||
-l, --license string name of license for the project
|
|
||||||
--viper use Viper for configuration (default true)
|
|
||||||
|
|
||||||
Use "cobra [command] --help" for more information about a command.
|
|
||||||
|
|
||||||
### Defining your own usage
|
|
||||||
You can provide your own usage function or template for Cobra to use.
|
|
||||||
Like help, the function and template are overridable through public methods:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cmd.SetUsageFunc(f func(*Command) error)
|
|
||||||
cmd.SetUsageTemplate(s string)
|
|
||||||
```
|
|
||||||
|
|
||||||
## PreRun and PostRun Hooks
|
|
||||||
|
|
||||||
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order:
|
|
||||||
|
|
||||||
- `PersistentPreRun`
|
|
||||||
- `PreRun`
|
|
||||||
- `Run`
|
|
||||||
- `PostRun`
|
|
||||||
- `PersistentPostRun`
|
|
||||||
|
|
||||||
An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "root [sub]",
|
|
||||||
Short: "My root command",
|
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside rootCmd Run with args: %v\n", args)
|
|
||||||
},
|
|
||||||
PostRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmd = &cobra.Command{
|
|
||||||
Use: "sub [no options!]",
|
|
||||||
Short: "My subcommand",
|
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside subCmd Run with args: %v\n", args)
|
|
||||||
},
|
|
||||||
PostRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCmd.AddCommand(subCmd)
|
|
||||||
|
|
||||||
rootCmd.SetArgs([]string{""})
|
|
||||||
rootCmd.Execute()
|
|
||||||
fmt.Println()
|
|
||||||
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
|
|
||||||
rootCmd.Execute()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
```
|
|
||||||
Inside rootCmd PersistentPreRun with args: []
|
|
||||||
Inside rootCmd PreRun with args: []
|
|
||||||
Inside rootCmd Run with args: []
|
|
||||||
Inside rootCmd PostRun with args: []
|
|
||||||
Inside rootCmd PersistentPostRun with args: []
|
|
||||||
|
|
||||||
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
|
|
||||||
Inside subCmd PreRun with args: [arg1 arg2]
|
|
||||||
Inside subCmd Run with args: [arg1 arg2]
|
|
||||||
Inside subCmd PostRun with args: [arg1 arg2]
|
|
||||||
Inside subCmd PersistentPostRun with args: [arg1 arg2]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Suggestions when "unknown command" happens
|
|
||||||
|
|
||||||
Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ hugo srever
|
|
||||||
Error: unknown command "srever" for "hugo"
|
|
||||||
|
|
||||||
Did you mean this?
|
|
||||||
server
|
|
||||||
|
|
||||||
Run 'hugo --help' for usage.
|
|
||||||
```
|
|
||||||
|
|
||||||
Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
|
|
||||||
|
|
||||||
If you need to disable suggestions or tweak the string distance in your command, use:
|
|
||||||
|
|
||||||
```go
|
|
||||||
command.DisableSuggestions = true
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```go
|
|
||||||
command.SuggestionsMinimumDistance = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ kubectl remove
|
|
||||||
Error: unknown command "remove" for "kubectl"
|
|
||||||
|
|
||||||
Did you mean this?
|
|
||||||
delete
|
|
||||||
|
|
||||||
Run 'kubectl help' for usage.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generating documentation for your command
|
|
||||||
|
|
||||||
Cobra can generate documentation based on subcommands, flags, etc. in the following formats:
|
|
||||||
|
|
||||||
- [Markdown](doc/md_docs.md)
|
|
||||||
- [ReStructured Text](doc/rest_docs.md)
|
|
||||||
- [Man Page](doc/man_docs.md)
|
|
||||||
|
|
||||||
## Generating bash completions
|
|
||||||
|
|
||||||
Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md).
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
1. Fork it
|
|
||||||
2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`)
|
|
||||||
3. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
4. Make changes and add them (`git add .`)
|
|
||||||
5. Commit your changes (`git commit -m 'Add some feature'`)
|
|
||||||
6. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
7. Create new pull request
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt)
|
|
98
vendor/github.com/spf13/cobra/args.go
generated
vendored
98
vendor/github.com/spf13/cobra/args.go
generated
vendored
@ -1,98 +0,0 @@
|
|||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PositionalArgs func(cmd *Command, args []string) error
|
|
||||||
|
|
||||||
// Legacy arg validation has the following behaviour:
|
|
||||||
// - root commands with no subcommands can take arbitrary arguments
|
|
||||||
// - root commands with subcommands will do subcommand validity checking
|
|
||||||
// - subcommands will always accept arbitrary arguments
|
|
||||||
func legacyArgs(cmd *Command, args []string) error {
|
|
||||||
// no subcommand, always take args
|
|
||||||
if !cmd.HasSubCommands() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// root command with subcommands, do subcommand checking
|
|
||||||
if !cmd.HasParent() && len(args) > 0 {
|
|
||||||
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoArgs returns an error if any args are included
|
|
||||||
func NoArgs(cmd *Command, args []string) error {
|
|
||||||
if len(args) > 0 {
|
|
||||||
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs
|
|
||||||
func OnlyValidArgs(cmd *Command, args []string) error {
|
|
||||||
if len(cmd.ValidArgs) > 0 {
|
|
||||||
for _, v := range args {
|
|
||||||
if !stringInSlice(v, cmd.ValidArgs) {
|
|
||||||
return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringInSlice(a string, list []string) bool {
|
|
||||||
for _, b := range list {
|
|
||||||
if b == a {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArbitraryArgs never returns an error
|
|
||||||
func ArbitraryArgs(cmd *Command, args []string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinimumNArgs returns an error if there is not at least N args
|
|
||||||
func MinimumNArgs(n int) PositionalArgs {
|
|
||||||
return func(cmd *Command, args []string) error {
|
|
||||||
if len(args) < n {
|
|
||||||
return fmt.Errorf("requires at least %d arg(s), only received %d", n, len(args))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaximumNArgs returns an error if there are more than N args
|
|
||||||
func MaximumNArgs(n int) PositionalArgs {
|
|
||||||
return func(cmd *Command, args []string) error {
|
|
||||||
if len(args) > n {
|
|
||||||
return fmt.Errorf("accepts at most %d arg(s), received %d", n, len(args))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExactArgs returns an error if there are not exactly n args
|
|
||||||
func ExactArgs(n int) PositionalArgs {
|
|
||||||
return func(cmd *Command, args []string) error {
|
|
||||||
if len(args) != n {
|
|
||||||
return fmt.Errorf("accepts %d arg(s), received %d", n, len(args))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeArgs returns an error if the number of args is not within the expected range
|
|
||||||
func RangeArgs(min int, max int) PositionalArgs {
|
|
||||||
return func(cmd *Command, args []string) error {
|
|
||||||
if len(args) < min || len(args) > max {
|
|
||||||
return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
537
vendor/github.com/spf13/cobra/bash_completions.go
generated
vendored
537
vendor/github.com/spf13/cobra/bash_completions.go
generated
vendored
@ -1,537 +0,0 @@
|
|||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Annotations for Bash completion.
|
|
||||||
const (
|
|
||||||
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
|
|
||||||
BashCompCustom = "cobra_annotation_bash_completion_custom"
|
|
||||||
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
|
||||||
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writePreamble(buf *bytes.Buffer, name string) {
|
|
||||||
buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
|
|
||||||
buf.WriteString(`
|
|
||||||
__debug()
|
|
||||||
{
|
|
||||||
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
|
|
||||||
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
|
|
||||||
# _init_completion. This is a very minimal version of that function.
|
|
||||||
__my_init_completion()
|
|
||||||
{
|
|
||||||
COMPREPLY=()
|
|
||||||
_get_comp_words_by_ref "$@" cur prev words cword
|
|
||||||
}
|
|
||||||
|
|
||||||
__index_of_word()
|
|
||||||
{
|
|
||||||
local w word=$1
|
|
||||||
shift
|
|
||||||
index=0
|
|
||||||
for w in "$@"; do
|
|
||||||
[[ $w = "$word" ]] && return
|
|
||||||
index=$((index+1))
|
|
||||||
done
|
|
||||||
index=-1
|
|
||||||
}
|
|
||||||
|
|
||||||
__contains_word()
|
|
||||||
{
|
|
||||||
local w word=$1; shift
|
|
||||||
for w in "$@"; do
|
|
||||||
[[ $w = "$word" ]] && return
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_reply()
|
|
||||||
{
|
|
||||||
__debug "${FUNCNAME[0]}"
|
|
||||||
case $cur in
|
|
||||||
-*)
|
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
|
||||||
compopt -o nospace
|
|
||||||
fi
|
|
||||||
local allflags
|
|
||||||
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
|
|
||||||
allflags=("${must_have_one_flag[@]}")
|
|
||||||
else
|
|
||||||
allflags=("${flags[*]} ${two_word_flags[*]}")
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
|
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
|
||||||
[[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
|
|
||||||
fi
|
|
||||||
|
|
||||||
# complete after --flag=abc
|
|
||||||
if [[ $cur == *=* ]]; then
|
|
||||||
if [[ $(type -t compopt) = "builtin" ]]; then
|
|
||||||
compopt +o nospace
|
|
||||||
fi
|
|
||||||
|
|
||||||
local index flag
|
|
||||||
flag="${cur%%=*}"
|
|
||||||
__index_of_word "${flag}" "${flags_with_completion[@]}"
|
|
||||||
COMPREPLY=()
|
|
||||||
if [[ ${index} -ge 0 ]]; then
|
|
||||||
PREFIX=""
|
|
||||||
cur="${cur#*=}"
|
|
||||||
${flags_completion[${index}]}
|
|
||||||
if [ -n "${ZSH_VERSION}" ]; then
|
|
||||||
# zsh completion needs --flag= prefix
|
|
||||||
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 0;
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# check if we are handling a flag with special work handling
|
|
||||||
local index
|
|
||||||
__index_of_word "${prev}" "${flags_with_completion[@]}"
|
|
||||||
if [[ ${index} -ge 0 ]]; then
|
|
||||||
${flags_completion[${index}]}
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# we are parsing a flag and don't have a special handler, no completion
|
|
||||||
if [[ ${cur} != "${words[cword]}" ]]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local completions
|
|
||||||
completions=("${commands[@]}")
|
|
||||||
if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
|
|
||||||
completions=("${must_have_one_noun[@]}")
|
|
||||||
fi
|
|
||||||
if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
|
|
||||||
completions+=("${must_have_one_flag[@]}")
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
|
|
||||||
|
|
||||||
if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") )
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
|
||||||
declare -F __custom_func >/dev/null && __custom_func
|
|
||||||
fi
|
|
||||||
|
|
||||||
# available in bash-completion >= 2, not always present on macOS
|
|
||||||
if declare -F __ltrim_colon_completions >/dev/null; then
|
|
||||||
__ltrim_colon_completions "$cur"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# The arguments should be in the form "ext1|ext2|extn"
|
|
||||||
__handle_filename_extension_flag()
|
|
||||||
{
|
|
||||||
local ext="$1"
|
|
||||||
_filedir "@(${ext})"
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_subdirs_in_dir_flag()
|
|
||||||
{
|
|
||||||
local dir="$1"
|
|
||||||
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_flag()
|
|
||||||
{
|
|
||||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
|
||||||
|
|
||||||
# if a command required a flag, and we found it, unset must_have_one_flag()
|
|
||||||
local flagname=${words[c]}
|
|
||||||
local flagvalue
|
|
||||||
# if the word contained an =
|
|
||||||
if [[ ${words[c]} == *"="* ]]; then
|
|
||||||
flagvalue=${flagname#*=} # take in as flagvalue after the =
|
|
||||||
flagname=${flagname%%=*} # strip everything after the =
|
|
||||||
flagname="${flagname}=" # but put the = back
|
|
||||||
fi
|
|
||||||
__debug "${FUNCNAME[0]}: looking for ${flagname}"
|
|
||||||
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
|
|
||||||
must_have_one_flag=()
|
|
||||||
fi
|
|
||||||
|
|
||||||
# if you set a flag which only applies to this command, don't show subcommands
|
|
||||||
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
|
|
||||||
commands=()
|
|
||||||
fi
|
|
||||||
|
|
||||||
# keep flag value with flagname as flaghash
|
|
||||||
if [ -n "${flagvalue}" ] ; then
|
|
||||||
flaghash[${flagname}]=${flagvalue}
|
|
||||||
elif [ -n "${words[ $((c+1)) ]}" ] ; then
|
|
||||||
flaghash[${flagname}]=${words[ $((c+1)) ]}
|
|
||||||
else
|
|
||||||
flaghash[${flagname}]="true" # pad "true" for bool flag
|
|
||||||
fi
|
|
||||||
|
|
||||||
# skip the argument to a two word flag
|
|
||||||
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
|
|
||||||
c=$((c+1))
|
|
||||||
# if we are looking for a flags value, don't show commands
|
|
||||||
if [[ $c -eq $cword ]]; then
|
|
||||||
commands=()
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
c=$((c+1))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_noun()
|
|
||||||
{
|
|
||||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
|
||||||
|
|
||||||
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
|
|
||||||
must_have_one_noun=()
|
|
||||||
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then
|
|
||||||
must_have_one_noun=()
|
|
||||||
fi
|
|
||||||
|
|
||||||
nouns+=("${words[c]}")
|
|
||||||
c=$((c+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_command()
|
|
||||||
{
|
|
||||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
|
||||||
|
|
||||||
local next_command
|
|
||||||
if [[ -n ${last_command} ]]; then
|
|
||||||
next_command="_${last_command}_${words[c]//:/__}"
|
|
||||||
else
|
|
||||||
if [[ $c -eq 0 ]]; then
|
|
||||||
next_command="_$(basename "${words[c]//:/__}")"
|
|
||||||
else
|
|
||||||
next_command="_${words[c]//:/__}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
c=$((c+1))
|
|
||||||
__debug "${FUNCNAME[0]}: looking for ${next_command}"
|
|
||||||
declare -F "$next_command" >/dev/null && $next_command
|
|
||||||
}
|
|
||||||
|
|
||||||
__handle_word()
|
|
||||||
{
|
|
||||||
if [[ $c -ge $cword ]]; then
|
|
||||||
__handle_reply
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
|
|
||||||
if [[ "${words[c]}" == -* ]]; then
|
|
||||||
__handle_flag
|
|
||||||
elif __contains_word "${words[c]}" "${commands[@]}"; then
|
|
||||||
__handle_command
|
|
||||||
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
|
|
||||||
__handle_command
|
|
||||||
else
|
|
||||||
__handle_noun
|
|
||||||
fi
|
|
||||||
__handle_word
|
|
||||||
}
|
|
||||||
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePostscript(buf *bytes.Buffer, name string) {
|
|
||||||
name = strings.Replace(name, ":", "__", -1)
|
|
||||||
buf.WriteString(fmt.Sprintf("__start_%s()\n", name))
|
|
||||||
buf.WriteString(fmt.Sprintf(`{
|
|
||||||
local cur prev words cword
|
|
||||||
declare -A flaghash 2>/dev/null || :
|
|
||||||
if declare -F _init_completion >/dev/null 2>&1; then
|
|
||||||
_init_completion -s || return
|
|
||||||
else
|
|
||||||
__my_init_completion -n "=" || return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local c=0
|
|
||||||
local flags=()
|
|
||||||
local two_word_flags=()
|
|
||||||
local local_nonpersistent_flags=()
|
|
||||||
local flags_with_completion=()
|
|
||||||
local flags_completion=()
|
|
||||||
local commands=("%s")
|
|
||||||
local must_have_one_flag=()
|
|
||||||
local must_have_one_noun=()
|
|
||||||
local last_command
|
|
||||||
local nouns=()
|
|
||||||
|
|
||||||
__handle_word
|
|
||||||
}
|
|
||||||
|
|
||||||
`, name))
|
|
||||||
buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
|
|
||||||
complete -o default -F __start_%s %s
|
|
||||||
else
|
|
||||||
complete -o default -o nospace -F __start_%s %s
|
|
||||||
fi
|
|
||||||
|
|
||||||
`, name, name, name, name))
|
|
||||||
buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCommands(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
buf.WriteString(" commands=()\n")
|
|
||||||
for _, c := range cmd.Commands() {
|
|
||||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
|
|
||||||
}
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string) {
|
|
||||||
for key, value := range annotations {
|
|
||||||
switch key {
|
|
||||||
case BashCompFilenameExt:
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
|
||||||
|
|
||||||
var ext string
|
|
||||||
if len(value) > 0 {
|
|
||||||
ext = "__handle_filename_extension_flag " + strings.Join(value, "|")
|
|
||||||
} else {
|
|
||||||
ext = "_filedir"
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
|
|
||||||
case BashCompCustom:
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
|
||||||
if len(value) > 0 {
|
|
||||||
handlers := strings.Join(value, "; ")
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
|
|
||||||
} else {
|
|
||||||
buf.WriteString(" flags_completion+=(:)\n")
|
|
||||||
}
|
|
||||||
case BashCompSubdirsInDir:
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
|
|
||||||
|
|
||||||
var ext string
|
|
||||||
if len(value) == 1 {
|
|
||||||
ext = "__handle_subdirs_in_dir_flag " + value[0]
|
|
||||||
} else {
|
|
||||||
ext = "_filedir -d"
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) {
|
|
||||||
name := flag.Shorthand
|
|
||||||
format := " "
|
|
||||||
if len(flag.NoOptDefVal) == 0 {
|
|
||||||
format += "two_word_"
|
|
||||||
}
|
|
||||||
format += "flags+=(\"-%s\")\n"
|
|
||||||
buf.WriteString(fmt.Sprintf(format, name))
|
|
||||||
writeFlagHandler(buf, "-"+name, flag.Annotations)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) {
|
|
||||||
name := flag.Name
|
|
||||||
format := " flags+=(\"--%s"
|
|
||||||
if len(flag.NoOptDefVal) == 0 {
|
|
||||||
format += "="
|
|
||||||
}
|
|
||||||
format += "\")\n"
|
|
||||||
buf.WriteString(fmt.Sprintf(format, name))
|
|
||||||
writeFlagHandler(buf, "--"+name, flag.Annotations)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
|
|
||||||
name := flag.Name
|
|
||||||
format := " local_nonpersistent_flags+=(\"--%s"
|
|
||||||
if len(flag.NoOptDefVal) == 0 {
|
|
||||||
format += "="
|
|
||||||
}
|
|
||||||
format += "\")\n"
|
|
||||||
buf.WriteString(fmt.Sprintf(format, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFlags(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
buf.WriteString(` flags=()
|
|
||||||
two_word_flags=()
|
|
||||||
local_nonpersistent_flags=()
|
|
||||||
flags_with_completion=()
|
|
||||||
flags_completion=()
|
|
||||||
|
|
||||||
`)
|
|
||||||
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
|
|
||||||
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
||||||
if nonCompletableFlag(flag) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeFlag(buf, flag)
|
|
||||||
if len(flag.Shorthand) > 0 {
|
|
||||||
writeShortFlag(buf, flag)
|
|
||||||
}
|
|
||||||
if localNonPersistentFlags.Lookup(flag.Name) != nil {
|
|
||||||
writeLocalNonPersistentFlag(buf, flag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
||||||
if nonCompletableFlag(flag) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeFlag(buf, flag)
|
|
||||||
if len(flag.Shorthand) > 0 {
|
|
||||||
writeShortFlag(buf, flag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
buf.WriteString(" must_have_one_flag=()\n")
|
|
||||||
flags := cmd.NonInheritedFlags()
|
|
||||||
flags.VisitAll(func(flag *pflag.Flag) {
|
|
||||||
if nonCompletableFlag(flag) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for key := range flag.Annotations {
|
|
||||||
switch key {
|
|
||||||
case BashCompOneRequiredFlag:
|
|
||||||
format := " must_have_one_flag+=(\"--%s"
|
|
||||||
if flag.Value.Type() != "bool" {
|
|
||||||
format += "="
|
|
||||||
}
|
|
||||||
format += "\")\n"
|
|
||||||
buf.WriteString(fmt.Sprintf(format, flag.Name))
|
|
||||||
|
|
||||||
if len(flag.Shorthand) > 0 {
|
|
||||||
buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
buf.WriteString(" must_have_one_noun=()\n")
|
|
||||||
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
|
||||||
for _, value := range cmd.ValidArgs {
|
|
||||||
buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
buf.WriteString(" noun_aliases=()\n")
|
|
||||||
sort.Sort(sort.StringSlice(cmd.ArgAliases))
|
|
||||||
for _, value := range cmd.ArgAliases {
|
|
||||||
buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gen(buf *bytes.Buffer, cmd *Command) {
|
|
||||||
for _, c := range cmd.Commands() {
|
|
||||||
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
gen(buf, c)
|
|
||||||
}
|
|
||||||
commandName := cmd.CommandPath()
|
|
||||||
commandName = strings.Replace(commandName, " ", "_", -1)
|
|
||||||
commandName = strings.Replace(commandName, ":", "__", -1)
|
|
||||||
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
|
|
||||||
buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
|
|
||||||
writeCommands(buf, cmd)
|
|
||||||
writeFlags(buf, cmd)
|
|
||||||
writeRequiredFlag(buf, cmd)
|
|
||||||
writeRequiredNouns(buf, cmd)
|
|
||||||
writeArgAliases(buf, cmd)
|
|
||||||
buf.WriteString("}\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenBashCompletion generates bash completion file and writes to the passed writer.
|
|
||||||
func (c *Command) GenBashCompletion(w io.Writer) error {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
writePreamble(buf, c.Name())
|
|
||||||
if len(c.BashCompletionFunction) > 0 {
|
|
||||||
buf.WriteString(c.BashCompletionFunction + "\n")
|
|
||||||
}
|
|
||||||
gen(buf, c)
|
|
||||||
writePostscript(buf, c.Name())
|
|
||||||
|
|
||||||
_, err := buf.WriteTo(w)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonCompletableFlag(flag *pflag.Flag) bool {
|
|
||||||
return flag.Hidden || len(flag.Deprecated) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenBashCompletionFile generates bash completion file.
|
|
||||||
func (c *Command) GenBashCompletionFile(filename string) error {
|
|
||||||
outFile, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
return c.GenBashCompletion(outFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
|
|
||||||
func (c *Command) MarkFlagRequired(name string) error {
|
|
||||||
return MarkFlagRequired(c.Flags(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
|
|
||||||
func (c *Command) MarkPersistentFlagRequired(name string) error {
|
|
||||||
return MarkFlagRequired(c.PersistentFlags(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
|
|
||||||
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
|
||||||
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
|
||||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
|
||||||
func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
|
|
||||||
return MarkFlagFilename(c.Flags(), name, extensions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
|
|
||||||
// Generated bash autocompletion will call the bash function f for the flag.
|
|
||||||
func (c *Command) MarkFlagCustom(name string, f string) error {
|
|
||||||
return MarkFlagCustom(c.Flags(), name, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
|
|
||||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
|
||||||
func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
|
||||||
return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
|
|
||||||
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
|
||||||
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
|
||||||
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists.
|
|
||||||
// Generated bash autocompletion will call the bash function f for the flag.
|
|
||||||
func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error {
|
|
||||||
return flags.SetAnnotation(name, BashCompCustom, []string{f})
|
|
||||||
}
|
|
190
vendor/github.com/spf13/cobra/cobra.go
generated
vendored
190
vendor/github.com/spf13/cobra/cobra.go
generated
vendored
@ -1,190 +0,0 @@
|
|||||||
// Copyright © 2013 Steve Francia <spf@spf13.com>.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Commands similar to git, go tools and other modern CLI tools
|
|
||||||
// inspired by go, go-Commander, gh and subcommand
|
|
||||||
|
|
||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var templateFuncs = template.FuncMap{
|
|
||||||
"trim": strings.TrimSpace,
|
|
||||||
"trimRightSpace": trimRightSpace,
|
|
||||||
"trimTrailingWhitespaces": trimRightSpace,
|
|
||||||
"appendIfNotPresent": appendIfNotPresent,
|
|
||||||
"rpad": rpad,
|
|
||||||
"gt": Gt,
|
|
||||||
"eq": Eq,
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializers []func()
|
|
||||||
|
|
||||||
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
|
|
||||||
// to automatically enable in CLI tools.
|
|
||||||
// Set this to true to enable it.
|
|
||||||
var EnablePrefixMatching = false
|
|
||||||
|
|
||||||
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
|
|
||||||
// To disable sorting, set it to false.
|
|
||||||
var EnableCommandSorting = true
|
|
||||||
|
|
||||||
// MousetrapHelpText enables an information splash screen on Windows
|
|
||||||
// if the CLI is started from explorer.exe.
|
|
||||||
// To disable the mousetrap, just set this variable to blank string ("").
|
|
||||||
// Works only on Microsoft Windows.
|
|
||||||
var MousetrapHelpText string = `This is a command line tool.
|
|
||||||
|
|
||||||
You need to open cmd.exe and run it from there.
|
|
||||||
`
|
|
||||||
|
|
||||||
// AddTemplateFunc adds a template function that's available to Usage and Help
|
|
||||||
// template generation.
|
|
||||||
func AddTemplateFunc(name string, tmplFunc interface{}) {
|
|
||||||
templateFuncs[name] = tmplFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTemplateFuncs adds multiple template functions that are available to Usage and
|
|
||||||
// Help template generation.
|
|
||||||
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
|
|
||||||
for k, v := range tmplFuncs {
|
|
||||||
templateFuncs[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnInitialize takes a series of func() arguments and appends them to a slice of func().
|
|
||||||
func OnInitialize(y ...func()) {
|
|
||||||
initializers = append(initializers, y...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
|
||||||
|
|
||||||
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
|
|
||||||
// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
|
|
||||||
// ints and then compared.
|
|
||||||
func Gt(a interface{}, b interface{}) bool {
|
|
||||||
var left, right int64
|
|
||||||
av := reflect.ValueOf(a)
|
|
||||||
|
|
||||||
switch av.Kind() {
|
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
|
||||||
left = int64(av.Len())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
left = av.Int()
|
|
||||||
case reflect.String:
|
|
||||||
left, _ = strconv.ParseInt(av.String(), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
bv := reflect.ValueOf(b)
|
|
||||||
|
|
||||||
switch bv.Kind() {
|
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
|
||||||
right = int64(bv.Len())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
right = bv.Int()
|
|
||||||
case reflect.String:
|
|
||||||
right, _ = strconv.ParseInt(bv.String(), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return left > right
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
|
||||||
|
|
||||||
// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
|
|
||||||
func Eq(a interface{}, b interface{}) bool {
|
|
||||||
av := reflect.ValueOf(a)
|
|
||||||
bv := reflect.ValueOf(b)
|
|
||||||
|
|
||||||
switch av.Kind() {
|
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
|
||||||
panic("Eq called on unsupported type")
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return av.Int() == bv.Int()
|
|
||||||
case reflect.String:
|
|
||||||
return av.String() == bv.String()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimRightSpace(s string) string {
|
|
||||||
return strings.TrimRightFunc(s, unicode.IsSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
|
|
||||||
|
|
||||||
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
|
|
||||||
func appendIfNotPresent(s, stringToAppend string) string {
|
|
||||||
if strings.Contains(s, stringToAppend) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s + " " + stringToAppend
|
|
||||||
}
|
|
||||||
|
|
||||||
// rpad adds padding to the right of a string.
|
|
||||||
func rpad(s string, padding int) string {
|
|
||||||
template := fmt.Sprintf("%%-%ds", padding)
|
|
||||||
return fmt.Sprintf(template, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tmpl executes the given template text on data, writing the result to w.
|
|
||||||
func tmpl(w io.Writer, text string, data interface{}) error {
|
|
||||||
t := template.New("top")
|
|
||||||
t.Funcs(templateFuncs)
|
|
||||||
template.Must(t.Parse(text))
|
|
||||||
return t.Execute(w, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ld compares two strings and returns the levenshtein distance between them.
|
|
||||||
func ld(s, t string, ignoreCase bool) int {
|
|
||||||
if ignoreCase {
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
t = strings.ToLower(t)
|
|
||||||
}
|
|
||||||
d := make([][]int, len(s)+1)
|
|
||||||
for i := range d {
|
|
||||||
d[i] = make([]int, len(t)+1)
|
|
||||||
}
|
|
||||||
for i := range d {
|
|
||||||
d[i][0] = i
|
|
||||||
}
|
|
||||||
for j := range d[0] {
|
|
||||||
d[0][j] = j
|
|
||||||
}
|
|
||||||
for j := 1; j <= len(t); j++ {
|
|
||||||
for i := 1; i <= len(s); i++ {
|
|
||||||
if s[i-1] == t[j-1] {
|
|
||||||
d[i][j] = d[i-1][j-1]
|
|
||||||
} else {
|
|
||||||
min := d[i-1][j]
|
|
||||||
if d[i][j-1] < min {
|
|
||||||
min = d[i][j-1]
|
|
||||||
}
|
|
||||||
if d[i-1][j-1] < min {
|
|
||||||
min = d[i-1][j-1]
|
|
||||||
}
|
|
||||||
d[i][j] = min + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return d[len(s)][len(t)]
|
|
||||||
}
|
|
1409
vendor/github.com/spf13/cobra/command.go
generated
vendored
1409
vendor/github.com/spf13/cobra/command.go
generated
vendored
File diff suppressed because it is too large
Load Diff
5
vendor/github.com/spf13/cobra/command_notwin.go
generated
vendored
5
vendor/github.com/spf13/cobra/command_notwin.go
generated
vendored
@ -1,5 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package cobra
|
|
||||||
|
|
||||||
var preExecHookFn func(*Command)
|
|
20
vendor/github.com/spf13/cobra/command_win.go
generated
vendored
20
vendor/github.com/spf13/cobra/command_win.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/inconshreveable/mousetrap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var preExecHookFn = preExecHook
|
|
||||||
|
|
||||||
func preExecHook(c *Command) {
|
|
||||||
if MousetrapHelpText != "" && mousetrap.StartedByExplorer() {
|
|
||||||
c.Print(MousetrapHelpText)
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
126
vendor/github.com/spf13/cobra/zsh_completions.go
generated
vendored
126
vendor/github.com/spf13/cobra/zsh_completions.go
generated
vendored
@ -1,126 +0,0 @@
|
|||||||
package cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenZshCompletionFile generates zsh completion file.
|
|
||||||
func (c *Command) GenZshCompletionFile(filename string) error {
|
|
||||||
outFile, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
return c.GenZshCompletion(outFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenZshCompletion generates a zsh completion file and writes to the passed writer.
|
|
||||||
func (c *Command) GenZshCompletion(w io.Writer) error {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
writeHeader(buf, c)
|
|
||||||
maxDepth := maxDepth(c)
|
|
||||||
writeLevelMapping(buf, maxDepth)
|
|
||||||
writeLevelCases(buf, maxDepth, c)
|
|
||||||
|
|
||||||
_, err := buf.WriteTo(w)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeHeader(w io.Writer, cmd *Command) {
|
|
||||||
fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxDepth(c *Command) int {
|
|
||||||
if len(c.Commands()) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
maxDepthSub := 0
|
|
||||||
for _, s := range c.Commands() {
|
|
||||||
subDepth := maxDepth(s)
|
|
||||||
if subDepth > maxDepthSub {
|
|
||||||
maxDepthSub = subDepth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1 + maxDepthSub
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeLevelMapping(w io.Writer, numLevels int) {
|
|
||||||
fmt.Fprintln(w, `_arguments \`)
|
|
||||||
for i := 1; i <= numLevels; i++ {
|
|
||||||
fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i)
|
|
||||||
fmt.Fprintln(w)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files")
|
|
||||||
fmt.Fprintln(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeLevelCases(w io.Writer, maxDepth int, root *Command) {
|
|
||||||
fmt.Fprintln(w, "case $state in")
|
|
||||||
defer fmt.Fprintln(w, "esac")
|
|
||||||
|
|
||||||
for i := 1; i <= maxDepth; i++ {
|
|
||||||
fmt.Fprintf(w, " level%d)\n", i)
|
|
||||||
writeLevel(w, root, i)
|
|
||||||
fmt.Fprintln(w, " ;;")
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, " *)")
|
|
||||||
fmt.Fprintln(w, " _arguments '*: :_files'")
|
|
||||||
fmt.Fprintln(w, " ;;")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeLevel(w io.Writer, root *Command, i int) {
|
|
||||||
fmt.Fprintf(w, " case $words[%d] in\n", i)
|
|
||||||
defer fmt.Fprintln(w, " esac")
|
|
||||||
|
|
||||||
commands := filterByLevel(root, i)
|
|
||||||
byParent := groupByParent(commands)
|
|
||||||
|
|
||||||
for p, c := range byParent {
|
|
||||||
names := names(c)
|
|
||||||
fmt.Fprintf(w, " %s)\n", p)
|
|
||||||
fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " "))
|
|
||||||
fmt.Fprintln(w, " ;;")
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, " *)")
|
|
||||||
fmt.Fprintln(w, " _arguments '*: :_files'")
|
|
||||||
fmt.Fprintln(w, " ;;")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterByLevel(c *Command, l int) []*Command {
|
|
||||||
cs := make([]*Command, 0)
|
|
||||||
if l == 0 {
|
|
||||||
cs = append(cs, c)
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
for _, s := range c.Commands() {
|
|
||||||
cs = append(cs, filterByLevel(s, l-1)...)
|
|
||||||
}
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupByParent(commands []*Command) map[string][]*Command {
|
|
||||||
m := make(map[string][]*Command)
|
|
||||||
for _, c := range commands {
|
|
||||||
parent := c.Parent()
|
|
||||||
if parent == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m[parent.Name()] = append(m[parent.Name()], c)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func names(commands []*Command) []string {
|
|
||||||
ns := make([]string, len(commands))
|
|
||||||
for i, c := range commands {
|
|
||||||
ns[i] = c.Name()
|
|
||||||
}
|
|
||||||
return ns
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user