package main import ( gocontext "context" "encoding/csv" "fmt" "os" "os/signal" "strconv" "strings" "syscall" "github.com/Sirupsen/logrus" "github.com/containerd/containerd" containersapi "github.com/containerd/containerd/api/services/containers" contentapi "github.com/containerd/containerd/api/services/content" diffapi "github.com/containerd/containerd/api/services/diff" "github.com/containerd/containerd/api/services/events" "github.com/containerd/containerd/api/services/execution" imagesapi "github.com/containerd/containerd/api/services/images" namespacesapi "github.com/containerd/containerd/api/services/namespaces" snapshotapi "github.com/containerd/containerd/api/services/snapshot" versionservice "github.com/containerd/containerd/api/services/version" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" contentservice "github.com/containerd/containerd/services/content" "github.com/containerd/containerd/services/diff" imagesservice "github.com/containerd/containerd/services/images" namespacesservice "github.com/containerd/containerd/services/namespaces" snapshotservice "github.com/containerd/containerd/services/snapshot" "github.com/containerd/containerd/snapshot" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" "google.golang.org/grpc" ) var grpcConn *grpc.ClientConn // 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(clicontext *cli.Context) (gocontext.Context, gocontext.CancelFunc) { var ( ctx = gocontext.Background() timeout = clicontext.GlobalDuration("timeout") namespace = clicontext.GlobalString("namespace") cancel = func() {} ) ctx = namespaces.WithNamespace(ctx, namespace) if timeout > 0 { ctx, cancel = gocontext.WithTimeout(ctx, timeout) } else { ctx, cancel = gocontext.WithCancel(ctx) } return ctx, cancel } func getNamespacesService(clicontext *cli.Context) (namespaces.Store, error) { conn, err := getGRPCConnection(clicontext) if err != nil { return nil, err } return namespacesservice.NewStoreFromClient(namespacesapi.NewNamespacesClient(conn)), nil } func newClient(context *cli.Context) (*containerd.Client, error) { return containerd.New(context.GlobalString("address")) } func getContainersService(context *cli.Context) (containersapi.ContainersClient, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return containersapi.NewContainersClient(conn), nil } func getTasksService(context *cli.Context) (execution.TasksClient, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return execution.NewTasksClient(conn), nil } func getEventsService(context *cli.Context) (events.EventsClient, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return events.NewEventsClient(conn), nil } func getContentStore(context *cli.Context) (content.Store, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return contentservice.NewStoreFromClient(contentapi.NewContentClient(conn)), nil } func getSnapshotter(context *cli.Context) (snapshot.Snapshotter, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return snapshotservice.NewSnapshotterFromClient(snapshotapi.NewSnapshotsClient(conn)), nil } func getImageStore(clicontext *cli.Context) (images.Store, error) { conn, err := getGRPCConnection(clicontext) if err != nil { return nil, err } return imagesservice.NewStoreFromClient(imagesapi.NewImagesClient(conn)), nil } func getDiffService(context *cli.Context) (diff.DiffService, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return diff.NewDiffServiceFromClient(diffapi.NewDiffClient(conn)), nil } func getVersionService(context *cli.Context) (versionservice.VersionClient, error) { conn, err := getGRPCConnection(context) if err != nil { return nil, err } return versionservice.NewVersionClient(conn), nil } 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 } 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 } func stopCatch(sigc chan os.Signal) { signal.Stop(sigc) close(sigc) } // 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 } // replaceOrAppendEnvValues returns the defaults with the overrides either // replaced by env key or appended to the list func replaceOrAppendEnvValues(defaults, overrides []string) []string { cache := make(map[string]int, len(defaults)) for i, e := range defaults { parts := strings.SplitN(e, "=", 2) cache[parts[0]] = i } for _, value := range overrides { // Values w/o = means they want this env to be removed/unset. if !strings.Contains(value, "=") { if i, exists := cache[value]; exists { defaults[i] = "" // Used to indicate it should be removed } continue } // Just do a normal set/update parts := strings.SplitN(value, "=", 2) if i, exists := cache[parts[0]]; exists { defaults[i] = value } else { defaults = append(defaults, value) } } // Now remove all entries that we want to "unset" for i := 0; i < len(defaults); i++ { if defaults[i] == "" { defaults = append(defaults[:i], defaults[i+1:]...) i-- } } return defaults }