update containerd to v1.2.1, runc to v1.0.0-rc6
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>docker-19.03
parent
8cf9bec86a
commit
6ed72d683f
|
@ -252,7 +252,7 @@ export JAEGER_TRACE=0.0.0.0:6831
|
||||||
|
|
||||||
### Supported runc version
|
### Supported runc version
|
||||||
|
|
||||||
During development, BuildKit is tested with the version of runc that is being used by the containerd repository. Please refer to [runc.md](https://github.com/containerd/containerd/blob/v1.2.0-rc.1/RUNC.md) for more information.
|
During development, BuildKit is tested with the version of runc that is being used by the containerd repository. Please refer to [runc.md](https://github.com/containerd/containerd/blob/v1.2.1/RUNC.md) for more information.
|
||||||
|
|
||||||
### Running BuildKit without root privileges
|
### Running BuildKit without root privileges
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ type buildOpt struct {
|
||||||
func main() {
|
func main() {
|
||||||
var opt buildOpt
|
var opt buildOpt
|
||||||
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
||||||
flag.StringVar(&opt.containerd, "containerd", "v1.2.0-rc.1", "containerd version")
|
flag.StringVar(&opt.containerd, "containerd", "v1.2.1", "containerd version")
|
||||||
flag.StringVar(&opt.runc, "runc", "a00bf0190895aa465a5fbed0268888e2c8ddfe85", "runc version")
|
flag.StringVar(&opt.runc, "runc", "v1.0.0-rc6", "runc version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
bk := buildkit(opt)
|
bk := buildkit(opt)
|
||||||
|
|
|
@ -17,8 +17,8 @@ type buildOpt struct {
|
||||||
func main() {
|
func main() {
|
||||||
var opt buildOpt
|
var opt buildOpt
|
||||||
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
||||||
flag.StringVar(&opt.containerd, "containerd", "v1.2.0-rc.1", "containerd version")
|
flag.StringVar(&opt.containerd, "containerd", "v1.2.1", "containerd version")
|
||||||
flag.StringVar(&opt.runc, "runc", "a00bf0190895aa465a5fbed0268888e2c8ddfe85", "runc version")
|
flag.StringVar(&opt.runc, "runc", "v1.0.0-rc6", "runc version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
bk := buildkit(opt)
|
bk := buildkit(opt)
|
||||||
|
|
|
@ -17,8 +17,8 @@ type buildOpt struct {
|
||||||
func main() {
|
func main() {
|
||||||
var opt buildOpt
|
var opt buildOpt
|
||||||
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
||||||
flag.StringVar(&opt.containerd, "containerd", "v1.2.0-rc.1", "containerd version")
|
flag.StringVar(&opt.containerd, "containerd", "v1.2.1", "containerd version")
|
||||||
flag.StringVar(&opt.runc, "runc", "a00bf0190895aa465a5fbed0268888e2c8ddfe85", "runc version")
|
flag.StringVar(&opt.runc, "runc", "v1.0.0-rc6", "runc version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
bk := buildkit(opt)
|
bk := buildkit(opt)
|
||||||
|
|
|
@ -18,8 +18,8 @@ type buildOpt struct {
|
||||||
func main() {
|
func main() {
|
||||||
var opt buildOpt
|
var opt buildOpt
|
||||||
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
|
||||||
flag.StringVar(&opt.containerd, "containerd", "v1.2.0-rc.1", "containerd version")
|
flag.StringVar(&opt.containerd, "containerd", "v1.2.1", "containerd version")
|
||||||
flag.StringVar(&opt.runc, "runc", "a00bf0190895aa465a5fbed0268888e2c8ddfe85", "runc version")
|
flag.StringVar(&opt.runc, "runc", "v1.0.0-rc6", "runc version")
|
||||||
flag.StringVar(&opt.buildkit, "buildkit", "master", "buildkit version")
|
flag.StringVar(&opt.buildkit, "buildkit", "master", "buildkit version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ func run() error {
|
||||||
_ = buildkitd
|
_ = buildkitd
|
||||||
|
|
||||||
containerd, err := gb.BuildExe(gobuild.BuildOpt{
|
containerd, err := gb.BuildExe(gobuild.BuildOpt{
|
||||||
Source: llb.Git("github.com/containerd/containerd", "v1.2.0-rc.1"),
|
Source: llb.Git("github.com/containerd/containerd", "v1.2.1"),
|
||||||
MountPath: "/go/src/github.com/containerd/containerd",
|
MountPath: "/go/src/github.com/containerd/containerd",
|
||||||
Pkg: "github.com/containerd/containerd/cmd/containerd",
|
Pkg: "github.com/containerd/containerd/cmd/containerd",
|
||||||
BuildTags: []string{"no_btrfs"},
|
BuildTags: []string{"no_btrfs"},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
ARG RUNC_VERSION=a00bf0190895aa465a5fbed0268888e2c8ddfe85
|
ARG RUNC_VERSION=v1.0.0-rc6
|
||||||
ARG CONTAINERD_VERSION=v1.2.0-rc.1
|
ARG CONTAINERD_VERSION=v1.2.1
|
||||||
# containerd v1.0 for integration tests
|
# containerd v1.0 for integration tests
|
||||||
ARG CONTAINERD10_VERSION=v1.0.3
|
ARG CONTAINERD10_VERSION=v1.0.3
|
||||||
# available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only
|
# available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# syntax = tonistiigi/dockerfile:runmount20180925
|
# syntax = tonistiigi/dockerfile:runmount20180925
|
||||||
|
|
||||||
ARG RUNC_VERSION=a00bf0190895aa465a5fbed0268888e2c8ddfe85
|
ARG RUNC_VERSION=v1.0.0-rc6
|
||||||
ARG CONTAINERD_VERSION=v1.2.0-rc.1
|
ARG CONTAINERD_VERSION=v1.2.1
|
||||||
# containerd v1.0 for integration tests
|
# containerd v1.0 for integration tests
|
||||||
ARG CONTAINERD10_VERSION=v1.0.3
|
ARG CONTAINERD10_VERSION=v1.0.3
|
||||||
# available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only
|
# available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only
|
||||||
|
|
|
@ -6,10 +6,10 @@ github.com/davecgh/go-spew v1.1.0
|
||||||
github.com/pmezard/go-difflib v1.0.0
|
github.com/pmezard/go-difflib v1.0.0
|
||||||
golang.org/x/sys 1b2967e3c290b7c545b3db0deeda16e9be4f98a2
|
golang.org/x/sys 1b2967e3c290b7c545b3db0deeda16e9be4f98a2
|
||||||
|
|
||||||
github.com/containerd/containerd 1a5f9a3434ac53c0e9d27093ecc588e0c281c333
|
github.com/containerd/containerd 47b328aab79146a9e81e37704db60e7e04a09256
|
||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||||
github.com/sirupsen/logrus v1.0.0
|
github.com/sirupsen/logrus v1.0.3
|
||||||
google.golang.org/grpc v1.12.0
|
google.golang.org/grpc v1.12.0
|
||||||
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
||||||
golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd
|
golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd
|
||||||
|
@ -18,7 +18,7 @@ github.com/gogo/googleapis b23578765ee54ff6bceff57f397d833bf4ca6869
|
||||||
github.com/golang/protobuf v1.1.0
|
github.com/golang/protobuf v1.1.0
|
||||||
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
||||||
github.com/opencontainers/image-spec v1.0.1
|
github.com/opencontainers/image-spec v1.0.1
|
||||||
github.com/opencontainers/runc a00bf0190895aa465a5fbed0268888e2c8ddfe85
|
github.com/opencontainers/runc 96ec2177ae841256168fcf76954f7177af9446eb
|
||||||
github.com/Microsoft/go-winio v0.4.11
|
github.com/Microsoft/go-winio v0.4.11
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||||
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
|
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
|
||||||
|
@ -30,7 +30,7 @@ github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||||
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
|
||||||
github.com/Microsoft/hcsshim v0.7.9
|
github.com/Microsoft/hcsshim v0.7.9
|
||||||
golang.org/x/crypto 0709b304e793a5edb4a2c0145f281ecdc20838a4
|
golang.org/x/crypto 0709b304e793a5edb4a2c0145f281ecdc20838a4
|
||||||
github.com/containerd/cri 8506fe836677cc3bb23a16b68145128243d843b5 # release/1.2 branch
|
github.com/containerd/cri f913714917d2456d7e65a0be84962b1ce8acb487 # release/1.2 branch
|
||||||
|
|
||||||
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
||||||
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
|
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
![banner](https://github.com/containerd/containerd.io/blob/master/static/img/containerd-dark.png?raw=true)
|
![containerd banner](https://raw.githubusercontent.com/cncf/artwork/master/containerd/horizontal/color/containerd-horizontal-color.png)
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
|
[![GoDoc](https://godoc.org/github.com/containerd/containerd?status.svg)](https://godoc.org/github.com/containerd/containerd)
|
||||||
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
|
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
|
||||||
|
@ -224,7 +224,8 @@ This will be the best place to discuss design and implementation.
|
||||||
|
|
||||||
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
|
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
|
||||||
|
|
||||||
**Slack:** https://join.slack.com/t/dockercommunity/shared_invite/enQtNDM4NjAwNDMyOTUwLWZlMDZmYWRjZjk4Zjc5ZGQ5NWZkOWI1Yjk2NGE3ZWVlYjYxM2VhYjczOWIyZDFhZTE3NTUwZWQzMjhmNGYyZTg
|
**Slack:** Catch us in the #containerd and #containerd-dev channels on dockercommunity.slack.com.
|
||||||
|
[Click here for an invite to docker community slack.](https://join.slack.com/t/dockercommunity/shared_invite/enQtNDY4MDc1Mzc0MzIwLTgxZDBlMmM4ZGEyNDc1N2FkMzlhODJkYmE1YTVkYjM1MDE3ZjAwZjBkOGFlOTJkZjRmZGYzNjYyY2M3ZTUxYzQ)
|
||||||
|
|
||||||
### Reporting security issues
|
### Reporting security issues
|
||||||
|
|
||||||
|
@ -236,3 +237,21 @@ The containerd codebase is released under the [Apache 2.0 license](LICENSE.code)
|
||||||
The README.md file, and files in the "docs" folder are licensed under the
|
The README.md file, and files in the "docs" folder are licensed under the
|
||||||
Creative Commons Attribution 4.0 International License. You may obtain a
|
Creative Commons Attribution 4.0 International License. You may obtain a
|
||||||
copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
||||||
|
|
||||||
|
## Project details
|
||||||
|
|
||||||
|
**containerd** is the primary open source project within the broader containerd GitHub repository.
|
||||||
|
However, all projects within the repo have common maintainership, governance, and contributing
|
||||||
|
guidelines which are stored in a `project` repository commonly for all containerd projects.
|
||||||
|
|
||||||
|
Please find all these core project documents, including the:
|
||||||
|
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||||
|
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||||
|
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
||||||
|
|
||||||
|
## Adoption
|
||||||
|
|
||||||
|
Interested to see who is using containerd? Are you using containerd in a project?
|
||||||
|
Please add yourself via pull request to our [ADOPTERS.md](./ADOPTERS.md) file.
|
||||||
|
|
|
@ -100,7 +100,7 @@ const (
|
||||||
// readdir calls to this directory do not follow to lower layers.
|
// readdir calls to this directory do not follow to lower layers.
|
||||||
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
|
||||||
|
|
||||||
paxSchilyXattr = "SCHILY.xattrs."
|
paxSchilyXattr = "SCHILY.xattr."
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply applies a tar stream of an OCI style diff tar.
|
// Apply applies a tar stream of an OCI style diff tar.
|
||||||
|
@ -295,7 +295,7 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
|
||||||
linkBasename := filepath.Base(hdr.Linkname)
|
linkBasename := filepath.Base(hdr.Linkname)
|
||||||
srcHdr = aufsHardlinks[linkBasename]
|
srcHdr = aufsHardlinks[linkBasename]
|
||||||
if srcHdr == nil {
|
if srcHdr == nil {
|
||||||
return 0, fmt.Errorf("Invalid aufs hardlink")
|
return 0, fmt.Errorf("invalid aufs hardlink")
|
||||||
}
|
}
|
||||||
p, err := fs.RootPath(aufsTempdir, linkBasename)
|
p, err := fs.RootPath(aufsTempdir, linkBasename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build linux freebsd solaris
|
// +build freebsd linux openbsd solaris
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -520,6 +522,45 @@ func (c *Client) ListImages(ctx context.Context, filters ...string) ([]Image, er
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore restores a container from a checkpoint
|
||||||
|
func (c *Client) Restore(ctx context.Context, id string, checkpoint Image, opts ...RestoreOpts) (Container, error) {
|
||||||
|
store := c.ContentStore()
|
||||||
|
index, err := decodeIndex(ctx, store, checkpoint.Target())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done, err := c.WithLease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer done(ctx)
|
||||||
|
|
||||||
|
copts := []NewContainerOpts{}
|
||||||
|
for _, o := range opts {
|
||||||
|
copts = append(copts, o(ctx, id, c, checkpoint, index))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr, err := c.NewContainer(ctx, id, copts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIndex(ctx context.Context, index *ocispec.Index, client *Client, ref string) (d ocispec.Descriptor, err error) {
|
||||||
|
labels := map[string]string{}
|
||||||
|
for i, m := range index.Manifests {
|
||||||
|
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
return ocispec.Descriptor{}, err
|
||||||
|
}
|
||||||
|
return writeContent(ctx, client.ContentStore(), ocispec.MediaTypeImageIndex, ref, bytes.NewReader(data), content.WithLabels(labels))
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe to events that match one or more of the provided filters.
|
// Subscribe to events that match one or more of the provided filters.
|
||||||
//
|
//
|
||||||
// Callers should listen on both the envelope and errs channels. If the errs
|
// Callers should listen on both the envelope and errs channels. If the errs
|
||||||
|
|
|
@ -28,12 +28,22 @@ import (
|
||||||
"github.com/containerd/containerd/cio"
|
"github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
prototypes "github.com/gogo/protobuf/types"
|
prototypes "github.com/gogo/protobuf/types"
|
||||||
|
ver "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
checkpointImageNameLabel = "org.opencontainers.image.ref.name"
|
||||||
|
checkpointRuntimeNameLabel = "io.containerd.checkpoint.runtime"
|
||||||
|
checkpointSnapshotterNameLabel = "io.containerd.checkpoint.snapshotter"
|
||||||
|
)
|
||||||
|
|
||||||
// Container is a metadata object for container resources and task creation
|
// Container is a metadata object for container resources and task creation
|
||||||
type Container interface {
|
type Container interface {
|
||||||
// ID identifies the container
|
// ID identifies the container
|
||||||
|
@ -64,6 +74,8 @@ type Container interface {
|
||||||
Extensions(context.Context) (map[string]prototypes.Any, error)
|
Extensions(context.Context) (map[string]prototypes.Any, error)
|
||||||
// Update a container
|
// Update a container
|
||||||
Update(context.Context, ...UpdateContainerOpts) error
|
Update(context.Context, ...UpdateContainerOpts) error
|
||||||
|
// Checkpoint creates a checkpoint image of the current container
|
||||||
|
Checkpoint(context.Context, string, ...CheckpointOpts) (Image, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerFromRecord(client *Client, c containers.Container) *container {
|
func containerFromRecord(client *Client, c containers.Container) *container {
|
||||||
|
@ -272,6 +284,70 @@ func (c *container) Update(ctx context.Context, opts ...UpdateContainerOpts) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *container) Checkpoint(ctx context.Context, ref string, opts ...CheckpointOpts) (Image, error) {
|
||||||
|
index := &ocispec.Index{
|
||||||
|
Versioned: ver.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
Annotations: make(map[string]string),
|
||||||
|
}
|
||||||
|
copts := &options.CheckpointOptions{
|
||||||
|
Exit: false,
|
||||||
|
OpenTcp: false,
|
||||||
|
ExternalUnixSockets: false,
|
||||||
|
Terminal: false,
|
||||||
|
FileLocks: true,
|
||||||
|
EmptyNamespaces: nil,
|
||||||
|
}
|
||||||
|
info, err := c.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := c.Image(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done, err := c.client.WithLease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer done(ctx)
|
||||||
|
|
||||||
|
// add image name to manifest
|
||||||
|
index.Annotations[checkpointImageNameLabel] = img.Name()
|
||||||
|
// add runtime info to index
|
||||||
|
index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name
|
||||||
|
// add snapshotter info to index
|
||||||
|
index.Annotations[checkpointSnapshotterNameLabel] = info.Snapshotter
|
||||||
|
|
||||||
|
// process remaining opts
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ctx, c.client, &info, index, copts); err != nil {
|
||||||
|
err = errdefs.FromGRPC(err)
|
||||||
|
if !errdefs.IsAlreadyExists(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := writeIndex(ctx, index, c.client, c.ID()+"index")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i := images.Image{
|
||||||
|
Name: ref,
|
||||||
|
Target: desc,
|
||||||
|
}
|
||||||
|
checkpoint, err := c.client.ImageService().Create(ctx, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewImage(c.client, checkpoint), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
|
func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
|
||||||
response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
|
response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
|
||||||
ContainerID: c.id,
|
ContainerID: c.id,
|
||||||
|
|
155
vendor/github.com/containerd/containerd/container_checkpoint_opts.go
generated
vendored
Normal file
155
vendor/github.com/containerd/containerd/container_checkpoint_opts.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/diff"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/containerd/containerd/rootfs"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCheckpointRWUnsupported is returned if the container runtime does not support checkpoint
|
||||||
|
ErrCheckpointRWUnsupported = errors.New("rw checkpoint is only supported on v2 runtimes")
|
||||||
|
// ErrMediaTypeNotFound returns an error when a media type in the manifest is unknown
|
||||||
|
ErrMediaTypeNotFound = errors.New("media type not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckpointOpts are options to manage the checkpoint operation
|
||||||
|
type CheckpointOpts func(context.Context, *Client, *containers.Container, *imagespec.Index, *options.CheckpointOptions) error
|
||||||
|
|
||||||
|
// WithCheckpointImage includes the container image in the checkpoint
|
||||||
|
func WithCheckpointImage(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
ir, err := client.ImageService().Get(ctx, c.Image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, ir.Target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointTask includes the running task
|
||||||
|
func WithCheckpointTask(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
any, err := typeurl.MarshalAny(copts)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
task, err := client.TaskService().Checkpoint(ctx, &tasks.CheckpointTaskRequest{
|
||||||
|
ContainerID: c.ID,
|
||||||
|
Options: any,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, d := range task.Descriptors {
|
||||||
|
platformSpec := platforms.DefaultSpec()
|
||||||
|
index.Manifests = append(index.Manifests, imagespec.Descriptor{
|
||||||
|
MediaType: d.MediaType,
|
||||||
|
Size: d.Size_,
|
||||||
|
Digest: d.Digest,
|
||||||
|
Platform: &platformSpec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// save copts
|
||||||
|
data, err := any.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointOptions, c.ID+"-checkpoint-options", r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
desc.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, desc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointRuntime includes the container runtime info
|
||||||
|
func WithCheckpointRuntime(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
if c.Runtime.Options != nil {
|
||||||
|
data, err := c.Runtime.Options.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointRuntimeOptions, c.ID+"-runtime-options", r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
desc.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, desc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointRW includes the rw in the checkpoint
|
||||||
|
func WithCheckpointRW(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
diffOpts := []diff.Opt{
|
||||||
|
diff.WithReference(fmt.Sprintf("checkpoint-rw-%s", c.SnapshotKey)),
|
||||||
|
}
|
||||||
|
rw, err := rootfs.CreateDiff(ctx,
|
||||||
|
c.SnapshotKey,
|
||||||
|
client.SnapshotService(c.Snapshotter),
|
||||||
|
client.DiffService(),
|
||||||
|
diffOpts...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
rw.Platform = &imagespec.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
index.Manifests = append(index.Manifests, rw)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCheckpointTaskExit causes the task to exit after checkpoint
|
||||||
|
func WithCheckpointTaskExit(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error {
|
||||||
|
copts.Exit = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndexByMediaType returns the index in a manifest for the specified media type
|
||||||
|
func GetIndexByMediaType(index *imagespec.Index, mt string) (*imagespec.Descriptor, error) {
|
||||||
|
for _, d := range index.Manifests {
|
||||||
|
if d.MediaType == mt {
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrMediaTypeNotFound
|
||||||
|
}
|
|
@ -26,81 +26,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/images"
|
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/platforms"
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
protobuf "github.com/gogo/protobuf/types"
|
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithCheckpoint allows a container to be created from the checkpointed information
|
|
||||||
// provided by the descriptor. The image, snapshot, and runtime specifications are
|
|
||||||
// restored on the container
|
|
||||||
func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
|
|
||||||
// set image and rw, and spec
|
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
|
||||||
var (
|
|
||||||
desc = im.Target()
|
|
||||||
store = client.ContentStore()
|
|
||||||
)
|
|
||||||
index, err := decodeIndex(ctx, store, desc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var rw *v1.Descriptor
|
|
||||||
for _, m := range index.Manifests {
|
|
||||||
switch m.MediaType {
|
|
||||||
case v1.MediaTypeImageLayer:
|
|
||||||
fk := m
|
|
||||||
rw = &fk
|
|
||||||
case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList:
|
|
||||||
config, err := images.Config(ctx, store, m, platforms.Default())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to resolve image config")
|
|
||||||
}
|
|
||||||
diffIDs, err := images.RootFS(ctx, store, config)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to get rootfs")
|
|
||||||
}
|
|
||||||
setSnapshotterIfEmpty(c)
|
|
||||||
if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, snapshotKey, identity.ChainID(diffIDs).String()); err != nil {
|
|
||||||
if !errdefs.IsAlreadyExists(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Image = index.Annotations["image.name"]
|
|
||||||
case images.MediaTypeContainerd1CheckpointConfig:
|
|
||||||
data, err := content.ReadBlob(ctx, store, m)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to read checkpoint config")
|
|
||||||
}
|
|
||||||
var any protobuf.Any
|
|
||||||
if err := proto.Unmarshal(data, &any); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Spec = &any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rw != nil {
|
|
||||||
// apply the rw snapshot to the new rw layer
|
|
||||||
mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, snapshotKey)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to get mounts for %s", snapshotKey)
|
|
||||||
}
|
|
||||||
if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
|
|
||||||
return errors.Wrap(err, "unable to apply rw diff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.SnapshotKey = snapshotKey
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
|
||||||
// filesystem to be used by a container with user namespaces
|
// filesystem to be used by a container with user namespaces
|
||||||
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
||||||
|
|
150
vendor/github.com/containerd/containerd/container_restore_opts.go
generated
vendored
Normal file
150
vendor/github.com/containerd/containerd/container_restore_opts.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
ptypes "github.com/gogo/protobuf/types"
|
||||||
|
"github.com/opencontainers/image-spec/identity"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrImageNameNotFoundInIndex is returned when the image name is not found in the index
|
||||||
|
ErrImageNameNotFoundInIndex = errors.New("image name not found in index")
|
||||||
|
// ErrRuntimeNameNotFoundInIndex is returned when the runtime is not found in the index
|
||||||
|
ErrRuntimeNameNotFoundInIndex = errors.New("runtime not found in index")
|
||||||
|
// ErrSnapshotterNameNotFoundInIndex is returned when the snapshotter is not found in the index
|
||||||
|
ErrSnapshotterNameNotFoundInIndex = errors.New("snapshotter not found in index")
|
||||||
|
)
|
||||||
|
|
||||||
|
// RestoreOpts are options to manage the restore operation
|
||||||
|
type RestoreOpts func(context.Context, string, *Client, Image, *imagespec.Index) NewContainerOpts
|
||||||
|
|
||||||
|
// WithRestoreImage restores the image for the container
|
||||||
|
func WithRestoreImage(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
name, ok := index.Annotations[checkpointImageNameLabel]
|
||||||
|
if !ok || name == "" {
|
||||||
|
return ErrRuntimeNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
snapshotter, ok := index.Annotations[checkpointSnapshotterNameLabel]
|
||||||
|
if !ok || name == "" {
|
||||||
|
return ErrSnapshotterNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
i, err := client.GetImage(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parent := identity.ChainID(diffIDs).String()
|
||||||
|
if _, err := client.SnapshotService(snapshotter).Prepare(ctx, id, parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Image = i.Name()
|
||||||
|
c.SnapshotKey = id
|
||||||
|
c.Snapshotter = snapshotter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreRuntime restores the runtime for the container
|
||||||
|
func WithRestoreRuntime(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
name, ok := index.Annotations[checkpointRuntimeNameLabel]
|
||||||
|
if !ok {
|
||||||
|
return ErrRuntimeNameNotFoundInIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore options if present
|
||||||
|
m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointRuntimeOptions)
|
||||||
|
if err != nil {
|
||||||
|
if err != ErrMediaTypeNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var options *ptypes.Any
|
||||||
|
if m != nil {
|
||||||
|
store := client.ContentStore()
|
||||||
|
data, err := content.ReadBlob(ctx, store, *m)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to read checkpoint runtime")
|
||||||
|
}
|
||||||
|
if err := proto.Unmarshal(data, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Runtime = containers.RuntimeInfo{
|
||||||
|
Name: name,
|
||||||
|
Options: options,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreSpec restores the spec from the checkpoint for the container
|
||||||
|
func WithRestoreSpec(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store := client.ContentStore()
|
||||||
|
data, err := content.ReadBlob(ctx, store, *m)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to read checkpoint config")
|
||||||
|
}
|
||||||
|
var any ptypes.Any
|
||||||
|
if err := proto.Unmarshal(data, &any); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Spec = &any
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreRW restores the rw layer from the checkpoint for the container
|
||||||
|
func WithRestoreRW(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
// apply rw layer
|
||||||
|
rw, err := GetIndexByMediaType(index, imagespec.MediaTypeImageLayerGzip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, c.SnapshotKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,8 +110,9 @@ type IngestManager interface {
|
||||||
|
|
||||||
// Writer handles the write of content into a content store
|
// Writer handles the write of content into a content store
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
// Close is expected to be called after Commit() when commission is needed.
|
// Close closes the writer, if the writer has not been
|
||||||
// Closing a writer without commit allows resuming or aborting.
|
// committed this allows resuming or aborting.
|
||||||
|
// Calling Close on a closed writer will not error.
|
||||||
io.WriteCloser
|
io.WriteCloser
|
||||||
|
|
||||||
// Digest may return empty digest or panics until committed.
|
// Digest may return empty digest or panics until committed.
|
||||||
|
@ -119,6 +120,8 @@ type Writer interface {
|
||||||
|
|
||||||
// Commit commits the blob (but no roll-back is guaranteed on an error).
|
// Commit commits the blob (but no roll-back is guaranteed on an error).
|
||||||
// size and expected can be zero-value when unknown.
|
// size and expected can be zero-value when unknown.
|
||||||
|
// Commit always closes the writer, even on error.
|
||||||
|
// ErrAlreadyExists aborts the writer.
|
||||||
Commit(ctx context.Context, size int64, expected digest.Digest, opts ...Opt) error
|
Commit(ctx context.Context, size int64, expected digest.Digest, opts ...Opt) error
|
||||||
|
|
||||||
// Status returns the current state of write
|
// Status returns the current state of write
|
||||||
|
|
|
@ -524,12 +524,11 @@ func (s *store) writer(ctx context.Context, ref string, total int64, expected di
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
p := bufPool.Get().(*[]byte)
|
p := bufPool.Get().(*[]byte)
|
||||||
defer bufPool.Put(p)
|
|
||||||
|
|
||||||
offset, err = io.CopyBuffer(digester.Hash(), fp, *p)
|
offset, err = io.CopyBuffer(digester.Hash(), fp, *p)
|
||||||
|
bufPool.Put(p)
|
||||||
|
fp.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -80,43 +81,36 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.fp == nil {
|
// Ensure even on error the writer is fully closed
|
||||||
|
defer unlock(w.ref)
|
||||||
|
fp := w.fp
|
||||||
|
w.fp = nil
|
||||||
|
|
||||||
|
if fp == nil {
|
||||||
return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer")
|
return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := w.fp.Sync(); err != nil {
|
if err := fp.Sync(); err != nil {
|
||||||
|
fp.Close()
|
||||||
return errors.Wrap(err, "sync failed")
|
return errors.Wrap(err, "sync failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := w.fp.Stat()
|
fi, err := fp.Stat()
|
||||||
|
closeErr := fp.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "stat on ingest file failed")
|
return errors.Wrap(err, "stat on ingest file failed")
|
||||||
}
|
}
|
||||||
|
if closeErr != nil {
|
||||||
// change to readonly, more important for read, but provides _some_
|
return errors.Wrap(err, "failed to close ingest file")
|
||||||
// protection from this point on. We use the existing perms with a mask
|
|
||||||
// only allowing reads honoring the umask on creation.
|
|
||||||
//
|
|
||||||
// This removes write and exec, only allowing read per the creation umask.
|
|
||||||
//
|
|
||||||
// NOTE: Windows does not support this operation
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := w.fp.Chmod((fi.Mode() & os.ModePerm) &^ 0333); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to change ingest file permissions")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if size > 0 && size != fi.Size() {
|
if size > 0 && size != fi.Size() {
|
||||||
return errors.Errorf("unexpected commit size %d, expected %d", fi.Size(), size)
|
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", fi.Size(), size)
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.fp.Close(); err != nil {
|
|
||||||
return errors.Wrap(err, "failed closing ingest")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dgst := w.digester.Digest()
|
dgst := w.digester.Digest()
|
||||||
if expected != "" && expected != dgst {
|
if expected != "" && expected != dgst {
|
||||||
return errors.Errorf("unexpected commit digest %s, expected %s", dgst, expected)
|
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -129,27 +123,48 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up!!
|
|
||||||
defer os.RemoveAll(w.path)
|
|
||||||
|
|
||||||
if _, err := os.Stat(target); err == nil {
|
if _, err := os.Stat(target); err == nil {
|
||||||
// collision with the target file!
|
// collision with the target file!
|
||||||
|
if err := os.RemoveAll(w.path); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory")
|
||||||
|
}
|
||||||
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
|
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(ingest, target); err != nil {
|
if err := os.Rename(ingest, target); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ingest has now been made available in the content store, attempt to complete
|
||||||
|
// setting metadata but errors should only be logged and not returned since
|
||||||
|
// the content store cannot be cleanly rolled back.
|
||||||
|
|
||||||
commitTime := time.Now()
|
commitTime := time.Now()
|
||||||
if err := os.Chtimes(target, commitTime, commitTime); err != nil {
|
if err := os.Chtimes(target, commitTime, commitTime); err != nil {
|
||||||
return err
|
log.G(ctx).WithField("digest", dgst).Errorf("failed to change file time to commit time")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.fp = nil
|
// clean up!!
|
||||||
unlock(w.ref)
|
if err := os.RemoveAll(w.path); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).WithField("path", w.path).Errorf("failed to remove ingest directory")
|
||||||
|
}
|
||||||
|
|
||||||
if w.s.ls != nil && base.Labels != nil {
|
if w.s.ls != nil && base.Labels != nil {
|
||||||
if err := w.s.ls.Set(dgst, base.Labels); err != nil {
|
if err := w.s.ls.Set(dgst, base.Labels); err != nil {
|
||||||
return err
|
log.G(ctx).WithField("digest", dgst).Errorf("failed to set labels")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to readonly, more important for read, but provides _some_
|
||||||
|
// protection from this point on. We use the existing perms with a mask
|
||||||
|
// only allowing reads honoring the umask on creation.
|
||||||
|
//
|
||||||
|
// This removes write and exec, only allowing read per the creation umask.
|
||||||
|
//
|
||||||
|
// NOTE: Windows does not support this operation
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := os.Chmod(target, (fi.Mode()&os.ModePerm)&^0333); err != nil {
|
||||||
|
log.G(ctx).WithField("ref", w.ref).Errorf("failed to make readonly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,10 +37,10 @@ func WithProfile(profile string) oci.SpecOpts {
|
||||||
s.Linux.Seccomp = &specs.LinuxSeccomp{}
|
s.Linux.Seccomp = &specs.LinuxSeccomp{}
|
||||||
f, err := ioutil.ReadFile(profile)
|
f, err := ioutil.ReadFile(profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot load seccomp profile %q: %v", profile, err)
|
return fmt.Errorf("cannot load seccomp profile %q: %v", profile, err)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(f, s.Linux.Seccomp); err != nil {
|
if err := json.Unmarshal(f, s.Linux.Seccomp); err != nil {
|
||||||
return fmt.Errorf("Decoding seccomp profile failed %q: %v", profile, err)
|
return fmt.Errorf("decoding seccomp profile failed %q: %v", profile, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ type walkingDiff struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyDesc = ocispec.Descriptor{}
|
var emptyDesc = ocispec.Descriptor{}
|
||||||
|
var uncompressed = "containerd.io/uncompressed"
|
||||||
|
|
||||||
// NewWalkingDiff is a generic implementation of diff.Comparer. The diff is
|
// NewWalkingDiff is a generic implementation of diff.Comparer. The diff is
|
||||||
// calculated by mounting both the upper and lower mount sets and walking the
|
// calculated by mounting both the upper and lower mount sets and walking the
|
||||||
|
@ -125,7 +126,7 @@ func (s *walkingDiff) Compare(ctx context.Context, lower, upper []mount.Mount, o
|
||||||
if config.Labels == nil {
|
if config.Labels == nil {
|
||||||
config.Labels = map[string]string{}
|
config.Labels = map[string]string{}
|
||||||
}
|
}
|
||||||
config.Labels["containerd.io/uncompressed"] = dgstr.Digest().String()
|
config.Labels[uncompressed] = dgstr.Digest().String()
|
||||||
} else {
|
} else {
|
||||||
if err = archive.WriteDiff(ctx, cw, lowerRoot, upperRoot); err != nil {
|
if err = archive.WriteDiff(ctx, cw, lowerRoot, upperRoot); err != nil {
|
||||||
return errors.Wrap(err, "failed to write diff")
|
return errors.Wrap(err, "failed to write diff")
|
||||||
|
@ -149,6 +150,14 @@ func (s *walkingDiff) Compare(ctx context.Context, lower, upper []mount.Mount, o
|
||||||
return errors.Wrap(err, "failed to get info from content store")
|
return errors.Wrap(err, "failed to get info from content store")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set uncompressed label if digest already existed without label
|
||||||
|
if _, ok := info.Labels[uncompressed]; !ok {
|
||||||
|
info.Labels[uncompressed] = config.Labels[uncompressed]
|
||||||
|
if _, err := s.store.Update(ctx, info, "labels."+uncompressed); err != nil {
|
||||||
|
return errors.Wrap(err, "error setting uncompressed label")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ocidesc = ocispec.Descriptor{
|
ocidesc = ocispec.Descriptor{
|
||||||
MediaType: config.MediaType,
|
MediaType: config.MediaType,
|
||||||
Size: info.Size,
|
Size: info.Size,
|
||||||
|
|
|
@ -95,7 +95,7 @@ func FromGRPC(err error) error {
|
||||||
|
|
||||||
msg := rebaseMessage(cls, err)
|
msg := rebaseMessage(cls, err)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
err = errors.Wrapf(cls, msg)
|
err = errors.Wrap(cls, msg)
|
||||||
} else {
|
} else {
|
||||||
err = errors.WithStack(cls)
|
err = errors.WithStack(cls)
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,9 @@ func (e *eventRemote) Subscribe(ctx context.Context, filters ...string) (ch <-ch
|
||||||
Event: ev.Event,
|
Event: ev.Event,
|
||||||
}:
|
}:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
if cerr := ctx.Err(); cerr != context.Canceled {
|
||||||
|
errq <- cerr
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,10 +138,10 @@ func (e *Exchange) Subscribe(ctx context.Context, fs ...string) (ch <-chan *even
|
||||||
)
|
)
|
||||||
|
|
||||||
closeAll := func() {
|
closeAll := func() {
|
||||||
defer close(errq)
|
channel.Close()
|
||||||
defer e.broadcaster.Remove(dst)
|
queue.Close()
|
||||||
defer queue.Close()
|
e.broadcaster.Remove(dst)
|
||||||
defer channel.Close()
|
close(errq)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch = evch
|
ch = evch
|
||||||
|
|
|
@ -71,7 +71,7 @@ func ParseAll(ss ...string) (Filter, error) {
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
f, err := Parse(s)
|
f, err := Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error())
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = append(fs, f)
|
fs = append(fs, f)
|
||||||
|
|
|
@ -170,7 +170,6 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error {
|
||||||
chain = append(chain, layer.Diff.Digest)
|
chain = append(chain, layer.Diff.Digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if unpacked {
|
|
||||||
desc, err := i.i.Config(ctx, cs, i.platform)
|
desc, err := i.i.Config(ctx, cs, i.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -184,12 +183,9 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error {
|
||||||
fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs,
|
fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if _, err := cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
_, err = cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
|
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportIndex imports an index from a tar achive image bundle
|
// ImportIndex imports an index from a tar archive image bundle
|
||||||
// - implements Docker v1.1, v1.2 and OCI v1.
|
// - implements Docker v1.1, v1.2 and OCI v1.
|
||||||
// - prefers OCI v1 when provided
|
// - prefers OCI v1 when provided
|
||||||
// - creates OCI index for Docker formats
|
// - creates OCI index for Docker formats
|
||||||
|
@ -164,7 +164,7 @@ func ImportIndex(ctx context.Context, store content.Store, reader io.Reader) (oc
|
||||||
if len(platforms) > 0 {
|
if len(platforms) > 0 {
|
||||||
// Only one platform can be resolved from non-index manifest,
|
// Only one platform can be resolved from non-index manifest,
|
||||||
// The platform can only come from the config included above,
|
// The platform can only come from the config included above,
|
||||||
// if the config has no platform it can be safely ommitted.
|
// if the config has no platform it can be safely omitted.
|
||||||
desc.Platform = &platforms[0]
|
desc.Platform = &platforms[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,9 @@ const (
|
||||||
MediaTypeContainerd1Resource = "application/vnd.containerd.container.resource.tar"
|
MediaTypeContainerd1Resource = "application/vnd.containerd.container.resource.tar"
|
||||||
MediaTypeContainerd1RW = "application/vnd.containerd.container.rw.tar"
|
MediaTypeContainerd1RW = "application/vnd.containerd.container.rw.tar"
|
||||||
MediaTypeContainerd1CheckpointConfig = "application/vnd.containerd.container.checkpoint.config.v1+proto"
|
MediaTypeContainerd1CheckpointConfig = "application/vnd.containerd.container.checkpoint.config.v1+proto"
|
||||||
|
MediaTypeContainerd1CheckpointOptions = "application/vnd.containerd.container.checkpoint.options.v1+proto"
|
||||||
|
MediaTypeContainerd1CheckpointRuntimeName = "application/vnd.containerd.container.checkpoint.runtime.name"
|
||||||
|
MediaTypeContainerd1CheckpointRuntimeOptions = "application/vnd.containerd.container.checkpoint.runtime.options+proto"
|
||||||
// Legacy Docker schema1 manifest
|
// Legacy Docker schema1 manifest
|
||||||
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,7 +59,6 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
|
||||||
if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) {
|
if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) {
|
||||||
d := filepath.Dir(hdr.Name)
|
d := filepath.Dir(hdr.Name)
|
||||||
result := d == "bin"
|
result := d == "bin"
|
||||||
|
@ -73,8 +72,10 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
|
r.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
r.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (s *containerStore) List(ctx context.Context, fs ...string) ([]containers.C
|
||||||
|
|
||||||
filter, err := filters.ParseAll(fs...)
|
filter, err := filters.ParseAll(fs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error())
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bkt := getContainersBucket(s.tx, namespace)
|
bkt := getContainersBucket(s.tx, namespace)
|
||||||
|
|
|
@ -556,12 +556,6 @@ func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected dig
|
||||||
var innerErr error
|
var innerErr error
|
||||||
|
|
||||||
if err := update(ctx, nw.db, func(tx *bolt.Tx) error {
|
if err := update(ctx, nw.db, func(tx *bolt.Tx) error {
|
||||||
bkt := getIngestsBucket(tx, nw.namespace)
|
|
||||||
if bkt != nil {
|
|
||||||
if err := bkt.DeleteBucket([]byte(nw.ref)); err != nil && err != bolt.ErrBucketNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dgst, err := nw.commit(ctx, tx, size, expected, opts...)
|
dgst, err := nw.commit(ctx, tx, size, expected, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errdefs.IsAlreadyExists(err) {
|
if !errdefs.IsAlreadyExists(err) {
|
||||||
|
@ -569,6 +563,12 @@ func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected dig
|
||||||
}
|
}
|
||||||
innerErr = err
|
innerErr = err
|
||||||
}
|
}
|
||||||
|
bkt := getIngestsBucket(tx, nw.namespace)
|
||||||
|
if bkt != nil {
|
||||||
|
if err := bkt.DeleteBucket([]byte(nw.ref)); err != nil && err != bolt.ErrBucketNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := removeIngestLease(ctx, tx, nw.ref); err != nil {
|
if err := removeIngestLease(ctx, tx, nw.ref); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -584,30 +584,38 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64,
|
||||||
var base content.Info
|
var base content.Info
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
if err := opt(&base); err != nil {
|
if err := opt(&base); err != nil {
|
||||||
|
if nw.w != nil {
|
||||||
|
nw.w.Close()
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := validateInfo(&base); err != nil {
|
if err := validateInfo(&base); err != nil {
|
||||||
|
if nw.w != nil {
|
||||||
|
nw.w.Close()
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var actual digest.Digest
|
var actual digest.Digest
|
||||||
if nw.w == nil {
|
if nw.w == nil {
|
||||||
if size != 0 && size != nw.desc.Size {
|
if size != 0 && size != nw.desc.Size {
|
||||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, nw.desc.Size, size)
|
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, nw.desc.Size, size)
|
||||||
}
|
}
|
||||||
if expected != "" && expected != nw.desc.Digest {
|
if expected != "" && expected != nw.desc.Digest {
|
||||||
return "", errors.Errorf("%q unexpected digest", nw.ref)
|
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q unexpected digest", nw.ref)
|
||||||
}
|
}
|
||||||
size = nw.desc.Size
|
size = nw.desc.Size
|
||||||
actual = nw.desc.Digest
|
actual = nw.desc.Digest
|
||||||
} else {
|
} else {
|
||||||
status, err := nw.w.Status()
|
status, err := nw.w.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
nw.w.Close()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if size != 0 && size != status.Offset {
|
if size != 0 && size != status.Offset {
|
||||||
return "", errors.Errorf("%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
nw.w.Close()
|
||||||
|
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, status.Offset, size)
|
||||||
}
|
}
|
||||||
size = status.Offset
|
size = status.Offset
|
||||||
actual = nw.w.Digest()
|
actual = nw.w.Digest()
|
||||||
|
|
|
@ -84,7 +84,7 @@ func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, er
|
||||||
|
|
||||||
filter, err := filters.ParseAll(fs...)
|
filter, err := filters.ParseAll(fs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error())
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var m []images.Image
|
var m []images.Image
|
||||||
|
|
|
@ -122,7 +122,7 @@ func (lm *LeaseManager) List(ctx context.Context, fs ...string) ([]leases.Lease,
|
||||||
|
|
||||||
filter, err := filters.ParseAll(fs...)
|
filter, err := filters.ParseAll(fs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, err.Error())
|
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var ll []leases.Lease
|
var ll []leases.Lease
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build darwin freebsd
|
// +build darwin freebsd openbsd
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build freebsd openbsd
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
|
@ -68,7 +68,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
|
||||||
numFields := len(fields)
|
numFields := len(fields)
|
||||||
if numFields < 10 {
|
if numFields < 10 {
|
||||||
// should be at least 10 fields
|
// should be at least 10 fields
|
||||||
return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields)
|
return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
|
||||||
}
|
}
|
||||||
p := Info{}
|
p := Info{}
|
||||||
// ignore any numbers parsing errors, as there should not be any
|
// ignore any numbers parsing errors, as there should not be any
|
||||||
|
@ -76,7 +76,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
|
||||||
p.Parent, _ = strconv.Atoi(fields[1])
|
p.Parent, _ = strconv.Atoi(fields[1])
|
||||||
mm := strings.Split(fields[2], ":")
|
mm := strings.Split(fields[2], ":")
|
||||||
if len(mm) != 2 {
|
if len(mm) != 2 {
|
||||||
return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm)
|
return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm)
|
||||||
}
|
}
|
||||||
p.Major, _ = strconv.Atoi(mm[0])
|
p.Major, _ = strconv.Atoi(mm[0])
|
||||||
p.Minor, _ = strconv.Atoi(mm[1])
|
p.Minor, _ = strconv.Atoi(mm[1])
|
||||||
|
@ -101,11 +101,11 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == numFields {
|
if i == numFields {
|
||||||
return nil, fmt.Errorf("Parsing '%s' failed: missing separator ('-')", text)
|
return nil, fmt.Errorf("parsing '%s' failed: missing separator ('-')", text)
|
||||||
}
|
}
|
||||||
// There should be 3 fields after the separator...
|
// There should be 3 fields after the separator...
|
||||||
if i+4 > numFields {
|
if i+4 > numFields {
|
||||||
return nil, fmt.Errorf("Parsing '%s' failed: not enough fields after a separator", text)
|
return nil, fmt.Errorf("parsing '%s' failed: not enough fields after a separator", text)
|
||||||
}
|
}
|
||||||
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
|
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
|
||||||
// (like "//serv/My Documents") _may_ end up having a space in the last field
|
// (like "//serv/My Documents") _may_ end up having a space in the last field
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build !linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo
|
// +build !linux,!freebsd,!solaris,!openbsd freebsd,!cgo solaris,!cgo openbsd,!cgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
|
@ -209,6 +209,7 @@ func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
||||||
Linux: &specs.Linux{
|
Linux: &specs.Linux{
|
||||||
MaskedPaths: []string{
|
MaskedPaths: []string{
|
||||||
"/proc/acpi",
|
"/proc/acpi",
|
||||||
|
"/proc/asound",
|
||||||
"/proc/kcore",
|
"/proc/kcore",
|
||||||
"/proc/keys",
|
"/proc/keys",
|
||||||
"/proc/latency_stats",
|
"/proc/latency_stats",
|
||||||
|
@ -219,7 +220,6 @@ func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
||||||
"/proc/scsi",
|
"/proc/scsi",
|
||||||
},
|
},
|
||||||
ReadonlyPaths: []string{
|
ReadonlyPaths: []string{
|
||||||
"/proc/asound",
|
|
||||||
"/proc/bus",
|
"/proc/bus",
|
||||||
"/proc/fs",
|
"/proc/fs",
|
||||||
"/proc/irq",
|
"/proc/irq",
|
||||||
|
|
|
@ -1026,3 +1026,32 @@ func WithWindowsHyperV(_ context.Context, _ Client, _ *containers.Container, s *
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithMemoryLimit sets the `Linux.LinuxResources.Memory.Limit` section to the
|
||||||
|
// `limit` specified if the `Linux` section is not `nil`. Additionally sets the
|
||||||
|
// `Windows.WindowsResources.Memory.Limit` section if the `Windows` section is
|
||||||
|
// not `nil`.
|
||||||
|
func WithMemoryLimit(limit uint64) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Linux != nil {
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &specs.LinuxResources{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources.Memory == nil {
|
||||||
|
s.Linux.Resources.Memory = &specs.LinuxMemory{}
|
||||||
|
}
|
||||||
|
l := int64(limit)
|
||||||
|
s.Linux.Resources.Memory.Limit = &l
|
||||||
|
}
|
||||||
|
if s.Windows != nil {
|
||||||
|
if s.Windows.Resources == nil {
|
||||||
|
s.Windows.Resources = &specs.WindowsResources{}
|
||||||
|
}
|
||||||
|
if s.Windows.Resources.Memory == nil {
|
||||||
|
s.Windows.Resources.Memory = &specs.WindowsMemoryResources{}
|
||||||
|
}
|
||||||
|
s.Windows.Resources.Memory.Limit = &limit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// +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 oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithWindowsCPUCount sets the `Windows.Resources.CPU.Count` section to the
|
||||||
|
// `count` specified.
|
||||||
|
func WithWindowsCPUCount(count uint64) SpecOpts {
|
||||||
|
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||||
|
if s.Windows.Resources == nil {
|
||||||
|
s.Windows.Resources = &specs.WindowsResources{}
|
||||||
|
}
|
||||||
|
if s.Windows.Resources.CPU == nil {
|
||||||
|
s.Windows.Resources.CPU = &specs.WindowsCPUResources{}
|
||||||
|
}
|
||||||
|
s.Windows.Resources.CPU.Count = &count
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,12 +80,8 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
|
||||||
|
|
||||||
// TODO(dmcg): Store challenge, not token
|
// TODO(dmcg): Store challenge, not token
|
||||||
// Move token fetching to authorize
|
// Move token fetching to authorize
|
||||||
if err := a.setTokenAuth(ctx, host, c.parameters); err != nil {
|
return a.setTokenAuth(ctx, host, c.parameters)
|
||||||
return err
|
} else if c.scheme == basicAuth && a.credentials != nil {
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
} else if c.scheme == basicAuth {
|
|
||||||
// TODO: Resolve credentials on authorize
|
// TODO: Resolve credentials on authorize
|
||||||
username, secret, err := a.credentials(host)
|
username, secret, err := a.credentials(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -198,7 +194,11 @@ func (a *dockerAuthorizer) fetchTokenWithOAuth(ctx context.Context, to tokenOpti
|
||||||
form.Set("password", to.secret)
|
form.Set("password", to.secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := ctxhttp.PostForm(ctx, a.client, to.realm, form)
|
resp, err := ctxhttp.Post(
|
||||||
|
ctx, a.client, to.realm,
|
||||||
|
"application/x-www-form-urlencoded; charset=utf-8",
|
||||||
|
strings.NewReader(form.Encode()),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
|
"github.com/containerd/containerd/version"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
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"
|
||||||
|
@ -75,13 +76,16 @@ type ResolverOptions struct {
|
||||||
|
|
||||||
// Credentials provides username and secret given a host.
|
// Credentials provides username and secret given a host.
|
||||||
// If username is empty but a secret is given, that secret
|
// If username is empty but a secret is given, that secret
|
||||||
// is interpretted as a long lived token.
|
// is interpreted as a long lived token.
|
||||||
// Deprecated: use Authorizer
|
// Deprecated: use Authorizer
|
||||||
Credentials func(string) (string, string, error)
|
Credentials func(string) (string, string, error)
|
||||||
|
|
||||||
// Host provides the hostname given a namespace.
|
// Host provides the hostname given a namespace.
|
||||||
Host func(string) (string, error)
|
Host func(string) (string, error)
|
||||||
|
|
||||||
|
// Headers are the HTTP request header fields sent by the resolver
|
||||||
|
Headers http.Header
|
||||||
|
|
||||||
// PlainHTTP specifies to use plain http and not https
|
// PlainHTTP specifies to use plain http and not https
|
||||||
PlainHTTP bool
|
PlainHTTP bool
|
||||||
|
|
||||||
|
@ -105,6 +109,7 @@ func DefaultHost(ns string) (string, error) {
|
||||||
type dockerResolver struct {
|
type dockerResolver struct {
|
||||||
auth Authorizer
|
auth Authorizer
|
||||||
host func(string) (string, error)
|
host func(string) (string, error)
|
||||||
|
headers http.Header
|
||||||
plainHTTP bool
|
plainHTTP bool
|
||||||
client *http.Client
|
client *http.Client
|
||||||
tracker StatusTracker
|
tracker StatusTracker
|
||||||
|
@ -118,12 +123,27 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
|
||||||
if options.Host == nil {
|
if options.Host == nil {
|
||||||
options.Host = DefaultHost
|
options.Host = DefaultHost
|
||||||
}
|
}
|
||||||
|
if options.Headers == nil {
|
||||||
|
options.Headers = make(http.Header)
|
||||||
|
}
|
||||||
|
if _, ok := options.Headers["Accept"]; !ok {
|
||||||
|
// set headers for all the types we support for resolution.
|
||||||
|
options.Headers.Set("Accept", strings.Join([]string{
|
||||||
|
images.MediaTypeDockerSchema2Manifest,
|
||||||
|
images.MediaTypeDockerSchema2ManifestList,
|
||||||
|
ocispec.MediaTypeImageManifest,
|
||||||
|
ocispec.MediaTypeImageIndex, "*"}, ", "))
|
||||||
|
}
|
||||||
|
if _, ok := options.Headers["User-Agent"]; !ok {
|
||||||
|
options.Headers.Set("User-Agent", "containerd/"+version.Version)
|
||||||
|
}
|
||||||
if options.Authorizer == nil {
|
if options.Authorizer == nil {
|
||||||
options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
|
options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
|
||||||
}
|
}
|
||||||
return &dockerResolver{
|
return &dockerResolver{
|
||||||
auth: options.Authorizer,
|
auth: options.Authorizer,
|
||||||
host: options.Host,
|
host: options.Host,
|
||||||
|
headers: options.Headers,
|
||||||
plainHTTP: options.PlainHTTP,
|
plainHTTP: options.PlainHTTP,
|
||||||
client: options.Client,
|
client: options.Client,
|
||||||
tracker: options.Tracker,
|
tracker: options.Tracker,
|
||||||
|
@ -182,12 +202,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
||||||
return "", ocispec.Descriptor{}, err
|
return "", ocispec.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set headers for all the types we support for resolution.
|
req.Header = r.headers
|
||||||
req.Header.Set("Accept", strings.Join([]string{
|
|
||||||
images.MediaTypeDockerSchema2Manifest,
|
|
||||||
images.MediaTypeDockerSchema2ManifestList,
|
|
||||||
ocispec.MediaTypeImageManifest,
|
|
||||||
ocispec.MediaTypeImageIndex, "*"}, ", "))
|
|
||||||
|
|
||||||
log.G(ctx).Debug("resolving")
|
log.G(ctx).Debug("resolving")
|
||||||
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
|
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -42,7 +43,10 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const manifestSizeLimit = 8e6 // 8MB
|
const (
|
||||||
|
manifestSizeLimit = 8e6 // 8MB
|
||||||
|
labelDockerSchema1EmptyLayer = "containerd.io/docker.schema1.empty-layer"
|
||||||
|
)
|
||||||
|
|
||||||
type blobState struct {
|
type blobState struct {
|
||||||
diffID digest.Digest
|
diffID digest.Digest
|
||||||
|
@ -353,10 +357,11 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro
|
||||||
Digest: desc.Digest,
|
Digest: desc.Digest,
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"containerd.io/uncompressed": state.diffID.String(),
|
"containerd.io/uncompressed": state.diffID.String(),
|
||||||
|
labelDockerSchema1EmptyLayer: strconv.FormatBool(state.empty),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
|
if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed", fmt.Sprintf("labels.%s", labelDockerSchema1EmptyLayer)); err != nil {
|
||||||
return errors.Wrap(err, "failed to update uncompressed label")
|
return errors.Wrap(err, "failed to update uncompressed label")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +385,18 @@ func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descri
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bState := blobState{empty: false}
|
emptyVal, ok := cinfo.Labels[labelDockerSchema1EmptyLayer]
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty, err := strconv.ParseBool(emptyVal)
|
||||||
|
if err != nil {
|
||||||
|
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse bool from label %s: %v", labelDockerSchema1EmptyLayer, isEmpty)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bState := blobState{empty: isEmpty}
|
||||||
|
|
||||||
if bState.diffID, err = digest.Parse(diffID); err != nil {
|
if bState.diffID, err = digest.Parse(diffID); err != nil {
|
||||||
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
|
log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
|
||||||
|
|
|
@ -60,6 +60,8 @@ type CreateOptions struct {
|
||||||
ShimCgroup string `protobuf:"bytes,9,opt,name=shim_cgroup,json=shimCgroup,proto3" json:"shim_cgroup,omitempty"`
|
ShimCgroup string `protobuf:"bytes,9,opt,name=shim_cgroup,json=shimCgroup,proto3" json:"shim_cgroup,omitempty"`
|
||||||
IoUid uint32 `protobuf:"varint,10,opt,name=io_uid,json=ioUid,proto3" json:"io_uid,omitempty"`
|
IoUid uint32 `protobuf:"varint,10,opt,name=io_uid,json=ioUid,proto3" json:"io_uid,omitempty"`
|
||||||
IoGid uint32 `protobuf:"varint,11,opt,name=io_gid,json=ioGid,proto3" json:"io_gid,omitempty"`
|
IoGid uint32 `protobuf:"varint,11,opt,name=io_gid,json=ioGid,proto3" json:"io_gid,omitempty"`
|
||||||
|
CriuWorkPath string `protobuf:"bytes,12,opt,name=criu_work_path,json=criuWorkPath,proto3" json:"criu_work_path,omitempty"`
|
||||||
|
CriuImagePath string `protobuf:"bytes,13,opt,name=criu_image_path,json=criuImagePath,proto3" json:"criu_image_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CreateOptions) Reset() { *m = CreateOptions{} }
|
func (m *CreateOptions) Reset() { *m = CreateOptions{} }
|
||||||
|
@ -74,6 +76,8 @@ type CheckpointOptions struct {
|
||||||
FileLocks bool `protobuf:"varint,5,opt,name=file_locks,json=fileLocks,proto3" json:"file_locks,omitempty"`
|
FileLocks bool `protobuf:"varint,5,opt,name=file_locks,json=fileLocks,proto3" json:"file_locks,omitempty"`
|
||||||
EmptyNamespaces []string `protobuf:"bytes,6,rep,name=empty_namespaces,json=emptyNamespaces" json:"empty_namespaces,omitempty"`
|
EmptyNamespaces []string `protobuf:"bytes,6,rep,name=empty_namespaces,json=emptyNamespaces" json:"empty_namespaces,omitempty"`
|
||||||
CgroupsMode string `protobuf:"bytes,7,opt,name=cgroups_mode,json=cgroupsMode,proto3" json:"cgroups_mode,omitempty"`
|
CgroupsMode string `protobuf:"bytes,7,opt,name=cgroups_mode,json=cgroupsMode,proto3" json:"cgroups_mode,omitempty"`
|
||||||
|
WorkPath string `protobuf:"bytes,8,opt,name=work_path,json=workPath,proto3" json:"work_path,omitempty"`
|
||||||
|
ImagePath string `protobuf:"bytes,9,opt,name=image_path,json=imagePath,proto3" json:"image_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CheckpointOptions) Reset() { *m = CheckpointOptions{} }
|
func (m *CheckpointOptions) Reset() { *m = CheckpointOptions{} }
|
||||||
|
@ -252,6 +256,18 @@ func (m *CreateOptions) MarshalTo(dAtA []byte) (int, error) {
|
||||||
i++
|
i++
|
||||||
i = encodeVarintRunc(dAtA, i, uint64(m.IoGid))
|
i = encodeVarintRunc(dAtA, i, uint64(m.IoGid))
|
||||||
}
|
}
|
||||||
|
if len(m.CriuWorkPath) > 0 {
|
||||||
|
dAtA[i] = 0x62
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CriuWorkPath)))
|
||||||
|
i += copy(dAtA[i:], m.CriuWorkPath)
|
||||||
|
}
|
||||||
|
if len(m.CriuImagePath) > 0 {
|
||||||
|
dAtA[i] = 0x6a
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CriuImagePath)))
|
||||||
|
i += copy(dAtA[i:], m.CriuImagePath)
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +357,18 @@ func (m *CheckpointOptions) MarshalTo(dAtA []byte) (int, error) {
|
||||||
i = encodeVarintRunc(dAtA, i, uint64(len(m.CgroupsMode)))
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.CgroupsMode)))
|
||||||
i += copy(dAtA[i:], m.CgroupsMode)
|
i += copy(dAtA[i:], m.CgroupsMode)
|
||||||
}
|
}
|
||||||
|
if len(m.WorkPath) > 0 {
|
||||||
|
dAtA[i] = 0x42
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.WorkPath)))
|
||||||
|
i += copy(dAtA[i:], m.WorkPath)
|
||||||
|
}
|
||||||
|
if len(m.ImagePath) > 0 {
|
||||||
|
dAtA[i] = 0x4a
|
||||||
|
i++
|
||||||
|
i = encodeVarintRunc(dAtA, i, uint64(len(m.ImagePath)))
|
||||||
|
i += copy(dAtA[i:], m.ImagePath)
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,6 +467,14 @@ func (m *CreateOptions) Size() (n int) {
|
||||||
if m.IoGid != 0 {
|
if m.IoGid != 0 {
|
||||||
n += 1 + sovRunc(uint64(m.IoGid))
|
n += 1 + sovRunc(uint64(m.IoGid))
|
||||||
}
|
}
|
||||||
|
l = len(m.CriuWorkPath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
|
l = len(m.CriuImagePath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,6 +506,14 @@ func (m *CheckpointOptions) Size() (n int) {
|
||||||
if l > 0 {
|
if l > 0 {
|
||||||
n += 1 + l + sovRunc(uint64(l))
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
}
|
}
|
||||||
|
l = len(m.WorkPath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
|
l = len(m.ImagePath)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovRunc(uint64(l))
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,6 +569,8 @@ func (this *CreateOptions) String() string {
|
||||||
`ShimCgroup:` + fmt.Sprintf("%v", this.ShimCgroup) + `,`,
|
`ShimCgroup:` + fmt.Sprintf("%v", this.ShimCgroup) + `,`,
|
||||||
`IoUid:` + fmt.Sprintf("%v", this.IoUid) + `,`,
|
`IoUid:` + fmt.Sprintf("%v", this.IoUid) + `,`,
|
||||||
`IoGid:` + fmt.Sprintf("%v", this.IoGid) + `,`,
|
`IoGid:` + fmt.Sprintf("%v", this.IoGid) + `,`,
|
||||||
|
`CriuWorkPath:` + fmt.Sprintf("%v", this.CriuWorkPath) + `,`,
|
||||||
|
`CriuImagePath:` + fmt.Sprintf("%v", this.CriuImagePath) + `,`,
|
||||||
`}`,
|
`}`,
|
||||||
}, "")
|
}, "")
|
||||||
return s
|
return s
|
||||||
|
@ -541,6 +587,8 @@ func (this *CheckpointOptions) String() string {
|
||||||
`FileLocks:` + fmt.Sprintf("%v", this.FileLocks) + `,`,
|
`FileLocks:` + fmt.Sprintf("%v", this.FileLocks) + `,`,
|
||||||
`EmptyNamespaces:` + fmt.Sprintf("%v", this.EmptyNamespaces) + `,`,
|
`EmptyNamespaces:` + fmt.Sprintf("%v", this.EmptyNamespaces) + `,`,
|
||||||
`CgroupsMode:` + fmt.Sprintf("%v", this.CgroupsMode) + `,`,
|
`CgroupsMode:` + fmt.Sprintf("%v", this.CgroupsMode) + `,`,
|
||||||
|
`WorkPath:` + fmt.Sprintf("%v", this.WorkPath) + `,`,
|
||||||
|
`ImagePath:` + fmt.Sprintf("%v", this.ImagePath) + `,`,
|
||||||
`}`,
|
`}`,
|
||||||
}, "")
|
}, "")
|
||||||
return s
|
return s
|
||||||
|
@ -994,6 +1042,64 @@ func (m *CreateOptions) Unmarshal(dAtA []byte) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 12:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field CriuWorkPath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.CriuWorkPath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 13:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field CriuImagePath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.CriuImagePath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipRunc(dAtA[iNdEx:])
|
skippy, err := skipRunc(dAtA[iNdEx:])
|
||||||
|
@ -1202,6 +1308,64 @@ func (m *CheckpointOptions) Unmarshal(dAtA []byte) error {
|
||||||
}
|
}
|
||||||
m.CgroupsMode = string(dAtA[iNdEx:postIndex])
|
m.CgroupsMode = string(dAtA[iNdEx:postIndex])
|
||||||
iNdEx = postIndex
|
iNdEx = postIndex
|
||||||
|
case 8:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field WorkPath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.WorkPath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 9:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field ImagePath", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRunc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthRunc
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.ImagePath = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipRunc(dAtA[iNdEx:])
|
skippy, err := skipRunc(dAtA[iNdEx:])
|
||||||
|
@ -1412,39 +1576,43 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptorRunc = []byte{
|
var fileDescriptorRunc = []byte{
|
||||||
// 541 bytes of a gzipped FileDescriptorProto
|
// 604 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x93, 0xc1, 0x6e, 0xd3, 0x40,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x94, 0xcf, 0x6e, 0xd3, 0x40,
|
||||||
0x10, 0x86, 0x6b, 0xda, 0x26, 0xce, 0xa4, 0x29, 0xb0, 0x50, 0xc9, 0x14, 0x91, 0x86, 0x00, 0x52,
|
0x10, 0xc6, 0xeb, 0xfe, 0x49, 0x9c, 0x49, 0xd2, 0xc2, 0x42, 0x25, 0xd3, 0xaa, 0x69, 0x08, 0x7f,
|
||||||
0xb8, 0xa4, 0x12, 0x88, 0x13, 0xb7, 0xa6, 0x08, 0x55, 0x40, 0xa9, 0x0c, 0x95, 0x10, 0x42, 0x5a,
|
0x14, 0x2e, 0xa9, 0x04, 0xe2, 0xc4, 0xad, 0x29, 0x42, 0x15, 0x50, 0x2a, 0x43, 0x05, 0x42, 0x48,
|
||||||
0xb9, 0xeb, 0x21, 0x59, 0xc5, 0xde, 0x59, 0x79, 0xd7, 0xd4, 0xb9, 0xf5, 0x09, 0x78, 0xae, 0x1e,
|
0x2b, 0x77, 0x3d, 0x24, 0xab, 0xc4, 0x3b, 0x96, 0x77, 0x4d, 0x92, 0x1b, 0x4f, 0xc0, 0x0b, 0xf1,
|
||||||
0x39, 0x72, 0x42, 0x34, 0x2f, 0x02, 0xf2, 0xda, 0x0e, 0x9c, 0x39, 0x72, 0xfb, 0xe7, 0xfb, 0xc7,
|
0x02, 0x3d, 0x21, 0x8e, 0x9c, 0x10, 0xcd, 0x93, 0xa0, 0x5d, 0xc7, 0x69, 0xcf, 0x1c, 0xb9, 0xcd,
|
||||||
0x9e, 0xd1, 0xbf, 0x1a, 0x98, 0x4c, 0xa5, 0x9d, 0xe5, 0x67, 0x63, 0x41, 0xe9, 0xbe, 0x20, 0x65,
|
0xfc, 0xe6, 0xb3, 0x67, 0xf4, 0x7d, 0xb2, 0xa1, 0x3f, 0x90, 0x66, 0x98, 0x9f, 0xf7, 0x04, 0x25,
|
||||||
0x23, 0xa9, 0x30, 0x8b, 0xff, 0x96, 0x59, 0xae, 0xac, 0x4c, 0x71, 0x3f, 0x91, 0x2a, 0x2f, 0xca,
|
0x07, 0x82, 0x94, 0x89, 0xa4, 0xc2, 0x2c, 0xbe, 0x5e, 0x66, 0xb9, 0x32, 0x32, 0xc1, 0x83, 0xb1,
|
||||||
0x4a, 0xd8, 0x85, 0x46, 0xe3, 0xd4, 0x58, 0x67, 0x64, 0x89, 0xed, 0xfc, 0x69, 0x1f, 0xbb, 0xb6,
|
0x54, 0xf9, 0xd4, 0x76, 0xc2, 0xcc, 0x52, 0xd4, 0xae, 0xea, 0xa5, 0x19, 0x19, 0x62, 0xdb, 0x57,
|
||||||
0x71, 0x69, 0xee, 0xde, 0x9e, 0xd2, 0x94, 0x5c, 0xc7, 0x7e, 0xa9, 0xaa, 0xe6, 0xe1, 0x57, 0x0f,
|
0xf2, 0x9e, 0x93, 0xf5, 0xec, 0x70, 0xe7, 0xf6, 0x80, 0x06, 0xe4, 0x14, 0x07, 0xb6, 0x2a, 0xc4,
|
||||||
0xba, 0x61, 0xae, 0xc4, 0x5b, 0x6d, 0x25, 0x29, 0xc3, 0x02, 0x68, 0xd7, 0x23, 0x02, 0x6f, 0xe0,
|
0x9d, 0x6f, 0x1e, 0xd4, 0xc3, 0x5c, 0x89, 0x37, 0xa9, 0x91, 0xa4, 0x34, 0x0b, 0xa0, 0xba, 0x58,
|
||||||
0x8d, 0x3a, 0x61, 0x53, 0xb2, 0xfb, 0xb0, 0x55, 0x4b, 0x9e, 0x11, 0xd9, 0xe0, 0x9a, 0xb3, 0xbb,
|
0x11, 0x78, 0x6d, 0xaf, 0x5b, 0x0b, 0xcb, 0x96, 0xdd, 0x85, 0xc6, 0xa2, 0xe4, 0x19, 0x91, 0x09,
|
||||||
0x35, 0x0b, 0x89, 0x2c, 0xbb, 0x0b, 0x1d, 0x91, 0xc9, 0x9c, 0xeb, 0xc8, 0xce, 0x82, 0x75, 0xe7,
|
0x56, 0xdd, 0xb8, 0xbe, 0x60, 0x21, 0x91, 0x61, 0xbb, 0x50, 0x13, 0x99, 0xcc, 0x79, 0x1a, 0x99,
|
||||||
0xfb, 0x25, 0x38, 0x89, 0xec, 0x8c, 0x3d, 0x82, 0x6d, 0xb3, 0x30, 0x16, 0xd3, 0x98, 0x8b, 0x69,
|
0x61, 0xb0, 0xe6, 0xe6, 0xbe, 0x05, 0xa7, 0x91, 0x19, 0xb2, 0x07, 0xb0, 0xa9, 0x67, 0xda, 0x60,
|
||||||
0x46, 0xb9, 0x0e, 0x36, 0x06, 0xde, 0xc8, 0x0f, 0x7b, 0x35, 0x9d, 0x38, 0x38, 0xbc, 0x58, 0x87,
|
0x12, 0x73, 0x31, 0xc8, 0x28, 0x4f, 0x83, 0xf5, 0xb6, 0xd7, 0xf5, 0xc3, 0xe6, 0x82, 0xf6, 0x1d,
|
||||||
0xde, 0x24, 0xc3, 0xc8, 0x62, 0xb3, 0xd2, 0x10, 0x7a, 0x8a, 0xb8, 0x96, 0x5f, 0xc8, 0x56, 0x93,
|
0xec, 0xfc, 0x58, 0x83, 0x66, 0x3f, 0xc3, 0xc8, 0x60, 0x79, 0x52, 0x07, 0x9a, 0x8a, 0x78, 0x2a,
|
||||||
0x3d, 0xf7, 0x5d, 0x57, 0xd1, 0x49, 0xc9, 0xdc, 0xe4, 0x3b, 0xe0, 0x93, 0x46, 0xc5, 0xad, 0xd0,
|
0xbf, 0x90, 0x29, 0x36, 0x7b, 0xee, 0xb9, 0xba, 0xa2, 0x53, 0xcb, 0xdc, 0xe6, 0x3b, 0xe0, 0x53,
|
||||||
0x6e, 0x31, 0x3f, 0x6c, 0x97, 0xf5, 0x7b, 0xa1, 0xd9, 0x13, 0xd8, 0xc1, 0xc2, 0x62, 0xa6, 0xa2,
|
0x8a, 0x8a, 0x1b, 0x91, 0xba, 0xc3, 0xfc, 0xb0, 0x6a, 0xfb, 0x77, 0x22, 0x65, 0x8f, 0x61, 0x1b,
|
||||||
0x84, 0xe7, 0x4a, 0x16, 0xdc, 0x90, 0x98, 0xa3, 0x35, 0x6e, 0x41, 0x3f, 0xbc, 0xd5, 0x98, 0xa7,
|
0xa7, 0x06, 0x33, 0x15, 0x8d, 0x79, 0xae, 0xe4, 0x94, 0x6b, 0x12, 0x23, 0x34, 0xda, 0x1d, 0xe8,
|
||||||
0x4a, 0x16, 0xef, 0x2a, 0x8b, 0xed, 0x82, 0x6f, 0x31, 0x4b, 0xa5, 0x8a, 0x92, 0x7a, 0xcb, 0x55,
|
0x87, 0xb7, 0xca, 0xe1, 0x99, 0x92, 0xd3, 0xb7, 0xc5, 0x88, 0xed, 0x80, 0x6f, 0x30, 0x4b, 0xa4,
|
||||||
0xcd, 0xee, 0x01, 0x7c, 0x96, 0x09, 0xf2, 0x84, 0xc4, 0xdc, 0x04, 0x9b, 0xce, 0xed, 0x94, 0xe4,
|
0x8a, 0xc6, 0x8b, 0x2b, 0x97, 0x3d, 0xdb, 0x03, 0xf8, 0x2c, 0xc7, 0xc8, 0xc7, 0x24, 0x46, 0x3a,
|
||||||
0x75, 0x09, 0xd8, 0x63, 0xb8, 0x81, 0xa9, 0xb6, 0x0b, 0xae, 0xa2, 0x14, 0x8d, 0x8e, 0x04, 0x9a,
|
0xd8, 0x70, 0xd3, 0x9a, 0x25, 0xaf, 0x2c, 0x60, 0x8f, 0xe0, 0x06, 0x26, 0xa9, 0x99, 0x71, 0x15,
|
||||||
0xa0, 0x35, 0x58, 0x1f, 0x75, 0xc2, 0xeb, 0x8e, 0x1f, 0xaf, 0x70, 0x99, 0x68, 0x95, 0x84, 0xe1,
|
0x25, 0xa8, 0xd3, 0x48, 0xa0, 0x0e, 0x2a, 0xed, 0xb5, 0x6e, 0x2d, 0xdc, 0x72, 0xfc, 0x64, 0x89,
|
||||||
0x29, 0xc5, 0x18, 0xb4, 0xab, 0x44, 0x6b, 0xf6, 0x86, 0x62, 0x64, 0x0f, 0x61, 0x5b, 0x11, 0x57,
|
0xad, 0xa3, 0x85, 0x13, 0x9a, 0x27, 0x14, 0x63, 0x50, 0x2d, 0x1c, 0x5d, 0xb0, 0xd7, 0x14, 0x23,
|
||||||
0x78, 0xce, 0xe7, 0xb8, 0xc8, 0xa4, 0x9a, 0x06, 0xbe, 0x1b, 0xb8, 0xa5, 0xe8, 0x18, 0xcf, 0x5f,
|
0xbb, 0x0f, 0x9b, 0x8a, 0xb8, 0xc2, 0x09, 0x1f, 0xe1, 0x2c, 0x93, 0x6a, 0x10, 0xf8, 0x6e, 0x61,
|
||||||
0x55, 0x8c, 0xed, 0x41, 0xd7, 0xcc, 0x64, 0xda, 0xe4, 0xda, 0x71, 0xff, 0x81, 0x12, 0x55, 0xa1,
|
0x43, 0xd1, 0x09, 0x4e, 0x5e, 0x16, 0x8c, 0xed, 0x43, 0x5d, 0x0f, 0x65, 0x52, 0xfa, 0x5a, 0x73,
|
||||||
0xb2, 0x1d, 0x68, 0x49, 0xe2, 0xb9, 0x8c, 0x03, 0x18, 0x78, 0xa3, 0x5e, 0xb8, 0x29, 0xe9, 0x54,
|
0xef, 0x01, 0x8b, 0x0a, 0x53, 0xd9, 0x36, 0x54, 0x24, 0xf1, 0x5c, 0xc6, 0x01, 0xb4, 0xbd, 0x6e,
|
||||||
0xc6, 0x35, 0x9e, 0xca, 0x38, 0xe8, 0x36, 0xf8, 0xa5, 0x8c, 0x87, 0xbf, 0x3c, 0xb8, 0x39, 0x99,
|
0x33, 0xdc, 0x90, 0x74, 0x26, 0xe3, 0x05, 0x1e, 0xc8, 0x38, 0xa8, 0x97, 0xf8, 0x85, 0x8c, 0xed,
|
||||||
0xa1, 0x98, 0x6b, 0x92, 0xca, 0x36, 0xcf, 0xc0, 0x60, 0x03, 0x0b, 0xd9, 0xa4, 0xef, 0xf4, 0xff,
|
0x52, 0x17, 0xe3, 0x84, 0xb2, 0x51, 0x91, 0x65, 0xc3, 0xbd, 0xb1, 0x61, 0xe9, 0x7b, 0xca, 0x46,
|
||||||
0x1a, 0xfb, 0xf0, 0x19, 0x6c, 0x9f, 0x64, 0x24, 0xd0, 0x98, 0x43, 0xb4, 0x91, 0x4c, 0x0c, 0x7b,
|
0x2e, 0xcf, 0x87, 0xb0, 0xe5, 0x54, 0x32, 0x89, 0x06, 0x58, 0xc8, 0x9a, 0x4e, 0xd6, 0xb4, 0xf8,
|
||||||
0x00, 0x6d, 0x2c, 0x50, 0x70, 0x19, 0x57, 0x77, 0x71, 0x00, 0xcb, 0x1f, 0x7b, 0xad, 0x17, 0x05,
|
0xd8, 0x52, 0xab, 0xeb, 0x7c, 0x5f, 0x85, 0x9b, 0xfd, 0x21, 0x8a, 0x51, 0x4a, 0x52, 0x99, 0x32,
|
||||||
0x8a, 0xa3, 0xc3, 0xb0, 0x55, 0x5a, 0x47, 0xf1, 0xc1, 0xa7, 0xcb, 0xab, 0xfe, 0xda, 0xf7, 0xab,
|
0x54, 0x06, 0xeb, 0x38, 0x95, 0x65, 0x96, 0xae, 0xfe, 0x6f, 0x43, 0xdc, 0x85, 0xda, 0x95, 0x95,
|
||||||
0xfe, 0xda, 0xc5, 0xb2, 0xef, 0x5d, 0x2e, 0xfb, 0xde, 0xb7, 0x65, 0xdf, 0xfb, 0xb9, 0xec, 0x7b,
|
0x7e, 0xf1, 0x59, 0x4c, 0x4a, 0x1b, 0xf7, 0x00, 0xae, 0x39, 0x58, 0x44, 0x57, 0x93, 0x4b, 0xf7,
|
||||||
0x1f, 0x0f, 0xfe, 0xf5, 0xb0, 0x9f, 0xaf, 0xd4, 0x87, 0xb5, 0xb3, 0x96, 0xbb, 0xd9, 0xa7, 0xbf,
|
0x9e, 0xc2, 0xe6, 0x69, 0x46, 0x02, 0xb5, 0x3e, 0x42, 0x13, 0xc9, 0xb1, 0x66, 0xf7, 0xa0, 0x8a,
|
||||||
0x03, 0x00, 0x00, 0xff, 0xff, 0x18, 0xa1, 0x4b, 0x5b, 0x27, 0x04, 0x00, 0x00,
|
0x53, 0x14, 0x5c, 0xc6, 0xc5, 0x17, 0x7a, 0x08, 0xf3, 0xdf, 0xfb, 0x95, 0xe7, 0x53, 0x14, 0xc7,
|
||||||
|
0x47, 0x61, 0xc5, 0x8e, 0x8e, 0xe3, 0xc3, 0x4f, 0x17, 0x97, 0xad, 0x95, 0x5f, 0x97, 0xad, 0x95,
|
||||||
|
0xaf, 0xf3, 0x96, 0x77, 0x31, 0x6f, 0x79, 0x3f, 0xe7, 0x2d, 0xef, 0xcf, 0xbc, 0xe5, 0x7d, 0x3c,
|
||||||
|
0xfc, 0xd7, 0x5f, 0xcc, 0xb3, 0x65, 0xf5, 0x61, 0xe5, 0xbc, 0xe2, 0xfe, 0x1e, 0x4f, 0xfe, 0x06,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x7f, 0x24, 0x6f, 0x2e, 0xb1, 0x04, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ message CreateOptions {
|
||||||
string shim_cgroup = 9;
|
string shim_cgroup = 9;
|
||||||
uint32 io_uid = 10;
|
uint32 io_uid = 10;
|
||||||
uint32 io_gid = 11;
|
uint32 io_gid = 11;
|
||||||
|
string criu_work_path = 12;
|
||||||
|
string criu_image_path = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CheckpointOptions {
|
message CheckpointOptions {
|
||||||
|
@ -35,6 +37,8 @@ message CheckpointOptions {
|
||||||
bool file_locks = 5;
|
bool file_locks = 5;
|
||||||
repeated string empty_namespaces = 6;
|
repeated string empty_namespaces = 6;
|
||||||
string cgroups_mode = 7;
|
string cgroups_mode = 7;
|
||||||
|
string work_path = 8;
|
||||||
|
string image_path = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProcessDetails {
|
message ProcessDetails {
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
# Runtime v2
|
||||||
|
|
||||||
|
Runtime v2 introduces a first class shim API for runtime authors to integrate with containerd.
|
||||||
|
The shim API is minimal and scoped to the execution lifecycle of a container.
|
||||||
|
|
||||||
|
## Binary Naming
|
||||||
|
|
||||||
|
Users specify the runtime they wish to use when creating a container.
|
||||||
|
The runtime can also be changed via a container update.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> ctr run --runtime io.containerd.runc.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
When a user specifies a runtime name, `io.containerd.runc.v1`, they will specify the name and version of the runtime.
|
||||||
|
This will be translated by containerd into a binary name for the shim.
|
||||||
|
|
||||||
|
`io.containerd.runc.v1` -> `containerd-shim-runc-v1`
|
||||||
|
|
||||||
|
containerd keeps the `containerd-shim-*` prefix so that users can `ps aux | grep containerd-shim` to see running shims on their system.
|
||||||
|
|
||||||
|
## Shim Authoring
|
||||||
|
|
||||||
|
This section is dedicated to runtime authors wishing to build a shim.
|
||||||
|
It will detail how the API works and different considerations when building shim.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
Container information is provided to a shim in two ways.
|
||||||
|
The OCI Runtime Bundle and on the `Create` rpc request.
|
||||||
|
|
||||||
|
#### `start`
|
||||||
|
|
||||||
|
Each shim MUST implement a `start` subcommand.
|
||||||
|
This command will launch new shims.
|
||||||
|
The start command MUST accept the following flags:
|
||||||
|
|
||||||
|
* `-namespace` the namespace for the container
|
||||||
|
* `-address` the address of the containerd's main socket
|
||||||
|
* `-publish-binary` the binary path to publish events back to containerd
|
||||||
|
* `-id` the id of the container
|
||||||
|
|
||||||
|
The start command, as well as all binary calls to the shim, has the bundle for the container set as the `cwd`.
|
||||||
|
|
||||||
|
The start command MUST return an address to a shim for containerd to issue API requests for container operations.
|
||||||
|
|
||||||
|
The start command can either start a new shim or return an address to an existing shim based on the shim's logic.
|
||||||
|
|
||||||
|
#### `delete`
|
||||||
|
|
||||||
|
Each shim MUST implement a `delete` subcommand.
|
||||||
|
This command allows containerd to delete any container resources created, mounted, and/or run by a shim when containerd can no longer communicate over rpc.
|
||||||
|
This happens if a shim is SIGKILL'd with a running container.
|
||||||
|
These resources will need to be cleaned up when containerd looses the connection to a shim.
|
||||||
|
This is also used when containerd boots and reconnects to shims.
|
||||||
|
If a bundle is still on disk but containerd cannot connect to a shim, the delete command is invoked.
|
||||||
|
|
||||||
|
The delete command MUST accept the following flags:
|
||||||
|
|
||||||
|
* `-namespace` the namespace for the container
|
||||||
|
* `-address` the address of the containerd's main socket
|
||||||
|
* `-publish-binary` the binary path to publish events back to containerd
|
||||||
|
* `-id` the id of the container
|
||||||
|
* `-bundle` the path to the bundle to delete. On non-Windows platforms this will match `cwd`
|
||||||
|
|
||||||
|
The delete command will be executed in the container's bundle as its `cwd` except for on the Windows platform.
|
||||||
|
|
||||||
|
### Host Level Shim Configuration
|
||||||
|
|
||||||
|
containerd does not provide any host level configuration for shims via the API.
|
||||||
|
If a shim needs configuration from the user with host level information across all instances, a shim specific configuration file can be setup.
|
||||||
|
|
||||||
|
### Container Level Shim Configuration
|
||||||
|
|
||||||
|
On the create request, there is a generic `*protobuf.Any` that allows a user to specify container level configuration for the shim.
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message CreateTaskRequest {
|
||||||
|
string id = 1;
|
||||||
|
...
|
||||||
|
google.protobuf.Any options = 10;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A shim author can create their own protobuf message for configuration and clients can import and provide this information is needed.
|
||||||
|
|
||||||
|
### I/O
|
||||||
|
|
||||||
|
I/O for a container is provided by the client to the shim via fifo on Linux, named pipes on Windows, or log files on disk.
|
||||||
|
The paths to these files are provided on the `Create` rpc for the initial creation and on the `Exec` rpc for additional processes.
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message CreateTaskRequest {
|
||||||
|
string id = 1;
|
||||||
|
bool terminal = 4;
|
||||||
|
string stdin = 5;
|
||||||
|
string stdout = 6;
|
||||||
|
string stderr = 7;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message ExecProcessRequest {
|
||||||
|
string id = 1;
|
||||||
|
string exec_id = 2;
|
||||||
|
bool terminal = 3;
|
||||||
|
string stdin = 4;
|
||||||
|
string stdout = 5;
|
||||||
|
string stderr = 6;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Containers that are to be launched with an interactive terminal will have the `terminal` field set to `true`, data is still copied over the files(fifos,pipes) in the same way as non interactive containers.
|
||||||
|
|
||||||
|
### Root Filesystems
|
||||||
|
|
||||||
|
The root filesystem for the containers is provided by on the `Create` rpc.
|
||||||
|
Shims are responsible for managing the lifecycle of the filesystem mount during the lifecycle of a container.
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message CreateTaskRequest {
|
||||||
|
string id = 1;
|
||||||
|
string bundle = 2;
|
||||||
|
repeated containerd.types.Mount rootfs = 3;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The mount protobuf message is:
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message Mount {
|
||||||
|
// Type defines the nature of the mount.
|
||||||
|
string type = 1;
|
||||||
|
// Source specifies the name of the mount. Depending on mount type, this
|
||||||
|
// may be a volume name or a host path, or even ignored.
|
||||||
|
string source = 2;
|
||||||
|
// Target path in container
|
||||||
|
string target = 3;
|
||||||
|
// Options specifies zero or more fstab style mount options.
|
||||||
|
repeated string options = 4;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Shims are responsible for mounting the filesystem into the `rootfs/` directory of the bundle.
|
||||||
|
Shims are also responsible for unmounting of the filesystem.
|
||||||
|
During a `delete` binary call, the shim MUST ensure that filesystem is also unmounted.
|
||||||
|
Filesystems are provided by the containerd snapshotters.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
The shim MUST publish a `runtime.TaskExitEventTopic` when the container exits.
|
||||||
|
If the shim collects Out of Memory events, it SHOULD also publish a `runtime.TaskOOMEventTopic`.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
#### Unsupported rpcs
|
||||||
|
|
||||||
|
If a shim does not or cannot implement an rpc call, it MUST return a `github.com/containerd/containerd/errdefs.ErrNotImplemented` error.
|
||||||
|
|
||||||
|
#### Debugging and Shim Logs
|
||||||
|
|
||||||
|
A fifo on unix or named pipe on Windows will be provided to the shim.
|
||||||
|
It can be located inside the `cwd` of the shim named "log".
|
||||||
|
The shims can use the existing `github.com/containerd/containerd/log` package to log debug messages.
|
||||||
|
Messages will automatically be output in the containerd's daemon logs with the correct fields and runtime set.
|
||||||
|
|
||||||
|
#### ttrpc
|
||||||
|
|
||||||
|
[ttrpc](https://github.com/containerd/ttrpc) is the only currently supported protocol for shims.
|
||||||
|
It works with standard protobufs and GRPC services as well as generating clients.
|
||||||
|
The only difference between grpc and ttrpc is the wire protocol.
|
||||||
|
ttrpc removes the http stack in order to save memory and binary size to keep shims small.
|
||||||
|
It is recommended to use ttrpc in your shim but grpc support is also in development.
|
17
vendor/github.com/containerd/containerd/runtime/v2/runc/options/doc.go
generated
vendored
Normal file
17
vendor/github.com/containerd/containerd/runtime/v2/runc/options/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
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 options
|
1313
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.pb.go
generated
vendored
Normal file
1313
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.proto
generated
vendored
Normal file
58
vendor/github.com/containerd/containerd/runtime/v2/runc/options/oci.proto
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package containerd.runc.v1;
|
||||||
|
|
||||||
|
import weak "gogoproto/gogo.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/containerd/containerd/runtime/v2/runc/options;options";
|
||||||
|
|
||||||
|
message Options {
|
||||||
|
// disable pivot root when creating a container
|
||||||
|
bool no_pivot_root = 1;
|
||||||
|
// create a new keyring for the container
|
||||||
|
bool no_new_keyring = 2;
|
||||||
|
// place the shim in a cgroup
|
||||||
|
string shim_cgroup = 3;
|
||||||
|
// set the I/O's pipes uid
|
||||||
|
uint32 io_uid = 4;
|
||||||
|
// set the I/O's pipes gid
|
||||||
|
uint32 io_gid = 5;
|
||||||
|
// binary name of the runc binary
|
||||||
|
string binary_name = 6;
|
||||||
|
// runc root directory
|
||||||
|
string root = 7;
|
||||||
|
// criu binary path
|
||||||
|
string criu_path = 8;
|
||||||
|
// enable systemd cgroups
|
||||||
|
bool systemd_cgroup = 9;
|
||||||
|
// criu image path
|
||||||
|
string criu_image_path = 10;
|
||||||
|
// criu work path
|
||||||
|
string criu_work_path = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckpointOptions {
|
||||||
|
// exit the container after a checkpoint
|
||||||
|
bool exit = 1;
|
||||||
|
// checkpoint open tcp connections
|
||||||
|
bool open_tcp = 2;
|
||||||
|
// checkpoint external unix sockets
|
||||||
|
bool external_unix_sockets = 3;
|
||||||
|
// checkpoint terminals (ptys)
|
||||||
|
bool terminal = 4;
|
||||||
|
// allow checkpointing of file locks
|
||||||
|
bool file_locks = 5;
|
||||||
|
// restore provided namespaces as empty namespaces
|
||||||
|
repeated string empty_namespaces = 6;
|
||||||
|
// set the cgroups mode, soft, full, strict
|
||||||
|
string cgroups_mode = 7;
|
||||||
|
// checkpoint image path
|
||||||
|
string image_path = 8;
|
||||||
|
// checkpoint work path
|
||||||
|
string work_path = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProcessDetails {
|
||||||
|
// exec process id if the process is managed by a shim
|
||||||
|
string exec_id = 1;
|
||||||
|
}
|
|
@ -426,7 +426,7 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) {
|
func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) {
|
||||||
td, err := ioutil.TempDir(filepath.Join(o.root, "snapshots"), "new-")
|
td, err := ioutil.TempDir(snapshotDir, "new-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to create temp dir")
|
return "", errors.Wrap(err, "failed to create temp dir")
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ import (
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/rootfs"
|
"github.com/containerd/containerd/rootfs"
|
||||||
|
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
google_protobuf "github.com/gogo/protobuf/types"
|
google_protobuf "github.com/gogo/protobuf/types"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
@ -147,6 +149,8 @@ type Task interface {
|
||||||
// OCI Index that can be push and pulled from a remote resource.
|
// OCI Index that can be push and pulled from a remote resource.
|
||||||
//
|
//
|
||||||
// Additional software like CRIU maybe required to checkpoint and restore tasks
|
// Additional software like CRIU maybe required to checkpoint and restore tasks
|
||||||
|
// NOTE: Checkpoint supports to dump task information to a directory, in this way,
|
||||||
|
// an empty OCI Index will be returned.
|
||||||
Checkpoint(context.Context, ...CheckpointTaskOpts) (Image, error)
|
Checkpoint(context.Context, ...CheckpointTaskOpts) (Image, error)
|
||||||
// Update modifies executing tasks with updated settings
|
// Update modifies executing tasks with updated settings
|
||||||
Update(context.Context, ...UpdateTaskOpts) error
|
Update(context.Context, ...UpdateTaskOpts) error
|
||||||
|
@ -387,6 +391,8 @@ func (t *task) Resize(ctx context.Context, w, h uint32) error {
|
||||||
return errdefs.FromGRPC(err)
|
return errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Checkpoint supports to dump task information to a directory, in this way, an empty
|
||||||
|
// OCI Index will be returned.
|
||||||
func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) {
|
func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Image, error) {
|
||||||
ctx, done, err := t.client.WithLease(ctx)
|
ctx, done, err := t.client.WithLease(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -433,6 +439,12 @@ func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Imag
|
||||||
if err := t.checkpointTask(ctx, &index, request); err != nil {
|
if err := t.checkpointTask(ctx, &index, request); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// if checkpoint image path passed, jump checkpoint image,
|
||||||
|
// return an empty image
|
||||||
|
if isCheckpointPathExist(cr.Runtime.Name, i.Options) {
|
||||||
|
return NewImage(t.client, images.Image{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
if cr.Image != "" {
|
if cr.Image != "" {
|
||||||
if err := t.checkpointImage(ctx, &index, cr.Image); err != nil {
|
if err := t.checkpointImage(ctx, &index, cr.Image); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -542,6 +554,7 @@ func (t *task) checkpointTask(ctx context.Context, index *v1.Index, request *tas
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errdefs.FromGRPC(err)
|
return errdefs.FromGRPC(err)
|
||||||
}
|
}
|
||||||
|
// NOTE: response.Descriptors can be an empty slice if checkpoint image is jumped
|
||||||
// add the checkpoint descriptors to the index
|
// add the checkpoint descriptors to the index
|
||||||
for _, d := range response.Descriptors {
|
for _, d := range response.Descriptors {
|
||||||
index.Manifests = append(index.Manifests, v1.Descriptor{
|
index.Manifests = append(index.Manifests, v1.Descriptor{
|
||||||
|
@ -619,3 +632,24 @@ func writeContent(ctx context.Context, store content.Ingester, mediaType, ref st
|
||||||
Size: size,
|
Size: size,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isCheckpointPathExist only suitable for runc runtime now
|
||||||
|
func isCheckpointPathExist(runtime string, v interface{}) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime {
|
||||||
|
case "io.containerd.runc.v1":
|
||||||
|
if opts, ok := v.(*options.CheckpointOptions); ok && opts.ImagePath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "io.containerd.runtime.v1.linux":
|
||||||
|
if opts, ok := v.(*runctypes.CheckpointOptions); ok && opts.ImagePath != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -27,11 +27,18 @@ 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/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||||
|
"github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
v1runtime = "io.containerd.runtime.v1.linux"
|
||||||
|
v2runtime = "io.containerd.runc.v1"
|
||||||
|
)
|
||||||
|
|
||||||
// NewTaskOpts allows the caller to set options on a new task
|
// NewTaskOpts allows the caller to set options on a new task
|
||||||
type NewTaskOpts func(context.Context, *Client, *TaskInfo) error
|
type NewTaskOpts func(context.Context, *Client, *TaskInfo) error
|
||||||
|
|
||||||
|
@ -89,6 +96,60 @@ func WithCheckpointName(name string) CheckpointTaskOpts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCheckpointImagePath sets image path for checkpoint option
|
||||||
|
func WithCheckpointImagePath(rt, path string) CheckpointTaskOpts {
|
||||||
|
return func(r *CheckpointTaskInfo) error {
|
||||||
|
switch rt {
|
||||||
|
case v1runtime:
|
||||||
|
if r.Options == nil {
|
||||||
|
r.Options = &runctypes.CheckpointOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := r.Options.(*runctypes.CheckpointOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v1 checkpoint options format")
|
||||||
|
}
|
||||||
|
opts.ImagePath = path
|
||||||
|
case v2runtime:
|
||||||
|
if r.Options == nil {
|
||||||
|
r.Options = &options.CheckpointOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := r.Options.(*options.CheckpointOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 checkpoint options format")
|
||||||
|
}
|
||||||
|
opts.ImagePath = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRestoreImagePath sets image path for create option
|
||||||
|
func WithRestoreImagePath(rt, path string) NewTaskOpts {
|
||||||
|
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
|
||||||
|
switch rt {
|
||||||
|
case v1runtime:
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &runctypes.CreateOptions{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*runctypes.CreateOptions)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v1 create options format")
|
||||||
|
}
|
||||||
|
opts.CriuImagePath = path
|
||||||
|
case v2runtime:
|
||||||
|
if ti.Options == nil {
|
||||||
|
ti.Options = &options.Options{}
|
||||||
|
}
|
||||||
|
opts, ok := ti.Options.(*options.Options)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid v2 create options format")
|
||||||
|
}
|
||||||
|
opts.CriuImagePath = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessDeleteOpts allows the caller to set options for the deletion of a task
|
// ProcessDeleteOpts allows the caller to set options for the deletion of a task
|
||||||
type ProcessDeleteOpts func(context.Context, Process) error
|
type ProcessDeleteOpts func(context.Context, Process) error
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
||||||
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
github.com/containerd/cgroups 1152b960fcee041f50df15cdc67c29dbccf801ef
|
||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||||
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
|
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
|
||||||
|
@ -20,8 +20,8 @@ github.com/gogo/protobuf v1.0.0
|
||||||
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
||||||
github.com/golang/protobuf v1.1.0
|
github.com/golang/protobuf v1.1.0
|
||||||
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
|
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
|
||||||
github.com/opencontainers/runc 00dc70017d222b178a002ed30e9321b12647af2d
|
github.com/opencontainers/runc 96ec2177ae841256168fcf76954f7177af9446eb
|
||||||
github.com/sirupsen/logrus v1.0.0
|
github.com/sirupsen/logrus v1.0.3
|
||||||
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
||||||
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
||||||
google.golang.org/grpc v1.12.0
|
google.golang.org/grpc v1.12.0
|
||||||
|
@ -33,7 +33,7 @@ golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||||
github.com/Microsoft/go-winio v0.4.11
|
github.com/Microsoft/go-winio v0.4.11
|
||||||
github.com/Microsoft/hcsshim v0.7.9
|
github.com/Microsoft/hcsshim v0.8.3
|
||||||
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||||
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
||||||
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
|
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
|
||||||
|
@ -43,8 +43,8 @@ github.com/google/go-cmp v0.1.0
|
||||||
go.etcd.io/bbolt v1.3.1-etcd.8
|
go.etcd.io/bbolt v1.3.1-etcd.8
|
||||||
|
|
||||||
# cri dependencies
|
# cri dependencies
|
||||||
github.com/containerd/cri 8506fe836677cc3bb23a16b68145128243d843b5 # release/1.2 branch
|
github.com/containerd/cri f913714917d2456d7e65a0be84962b1ce8acb487 # release/1.2 branch
|
||||||
github.com/containerd/go-cni 6d7b509a054a3cb1c35ed1865d4fde2f0cb547cd
|
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
|
||||||
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.7.0
|
github.com/containernetworking/plugins v0.7.0
|
||||||
|
@ -81,9 +81,9 @@ k8s.io/kubernetes v1.12.0
|
||||||
k8s.io/utils cd34563cd63c2bd7c6fe88a73c4dcf34ed8a67cb
|
k8s.io/utils cd34563cd63c2bd7c6fe88a73c4dcf34ed8a67cb
|
||||||
|
|
||||||
# zfs dependencies
|
# zfs dependencies
|
||||||
github.com/containerd/zfs 9a0b8b8b5982014b729cd34eb7cd7a11062aa6ec
|
github.com/containerd/zfs 9f6ef3b1fe5144bd91fe5855b4eba81bc0d17d03
|
||||||
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2
|
||||||
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd
|
||||||
|
|
||||||
# aufs dependencies
|
# aufs dependencies
|
||||||
github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92
|
github.com/containerd/aufs da3cf16bfbe68ba8f114f1536a05c01528a25434
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Package is filled at linking time
|
||||||
|
Package = "github.com/containerd/containerd"
|
||||||
|
|
||||||
|
// Version holds the complete version number. Filled in at linking time.
|
||||||
|
Version = "1.2.0+unknown"
|
||||||
|
|
||||||
|
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||||
|
// the program at linking time.
|
||||||
|
Revision = ""
|
||||||
|
)
|
|
@ -3,10 +3,10 @@ github.com/blang/semver v3.1.0
|
||||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
||||||
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
|
||||||
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
|
||||||
github.com/containerd/containerd f88d3e5d6dfe9b7d7941ac5241649ad8240b9282
|
github.com/containerd/containerd 15f19d7a67fa322e6de0ef4c6a1bf9da0f056554
|
||||||
github.com/containerd/continuity 7f53d412b9eb1cbf744c2063185d703a0ee34700
|
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||||
github.com/containerd/go-cni 6d7b509a054a3cb1c35ed1865d4fde2f0cb547cd
|
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
|
||||||
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||||
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
|
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
|
||||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
|
@ -34,7 +34,7 @@ github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
||||||
github.com/json-iterator/go 1.1.5
|
github.com/json-iterator/go 1.1.5
|
||||||
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.10
|
github.com/Microsoft/go-winio v0.4.10
|
||||||
github.com/Microsoft/hcsshim v0.7.4
|
github.com/Microsoft/hcsshim v0.7.6
|
||||||
github.com/modern-go/concurrent 1.0.3
|
github.com/modern-go/concurrent 1.0.3
|
||||||
github.com/modern-go/reflect2 1.0.1
|
github.com/modern-go/reflect2 1.0.1
|
||||||
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
||||||
|
|
|
@ -68,6 +68,7 @@ make BUILDTAGS='seccomp apparmor'
|
||||||
| selinux | selinux process and mount labeling | <none> |
|
| selinux | selinux process and mount labeling | <none> |
|
||||||
| apparmor | apparmor profile support | <none> |
|
| apparmor | apparmor profile support | <none> |
|
||||||
| ambient | ambient capability support | kernel 4.3 |
|
| ambient | ambient capability support | kernel 4.3 |
|
||||||
|
| nokmem | disable kernel memory account | <none> |
|
||||||
|
|
||||||
|
|
||||||
### Running the test suite
|
### Running the test suite
|
||||||
|
@ -263,3 +264,7 @@ PIDFile=/run/mycontainerid.pid
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The code and docs are released under the [Apache 2.0 license](LICENSE).
|
||||||
|
|
|
@ -148,6 +148,7 @@ config := &configs.Config{
|
||||||
{Type: configs.NEWPID},
|
{Type: configs.NEWPID},
|
||||||
{Type: configs.NEWUSER},
|
{Type: configs.NEWUSER},
|
||||||
{Type: configs.NEWNET},
|
{Type: configs.NEWNET},
|
||||||
|
{Type: configs.NEWCGROUP},
|
||||||
}),
|
}),
|
||||||
Cgroups: &configs.Cgroup{
|
Cgroups: &configs.Cgroup{
|
||||||
Name: "test-container",
|
Name: "test-container",
|
||||||
|
@ -323,6 +324,7 @@ generated when building libcontainer with docker.
|
||||||
|
|
||||||
## Copyright and license
|
## Copyright and license
|
||||||
|
|
||||||
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
|
Code and documentation copyright 2014 Docker, inc.
|
||||||
Docs released under Creative commons.
|
The code and documentation are released under the [Apache 2.0 license](../LICENSE).
|
||||||
|
The documentation is also released under Creative Commons Attribution 4.0 International License.
|
||||||
|
You may obtain a copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
||||||
|
|
|
@ -42,6 +42,12 @@ enum sync_t {
|
||||||
SYNC_ERR = 0xFF, /* Fatal error, no turning back. The error code follows. */
|
SYNC_ERR = 0xFF, /* Fatal error, no turning back. The error code follows. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Synchronisation value for cgroup namespace setup.
|
||||||
|
* The same constant is defined in process_linux.go as "createCgroupns".
|
||||||
|
*/
|
||||||
|
#define CREATECGROUPNS 0x80
|
||||||
|
|
||||||
/* longjmp() arguments. */
|
/* longjmp() arguments. */
|
||||||
#define JUMP_PARENT 0x00
|
#define JUMP_PARENT 0x00
|
||||||
#define JUMP_CHILD 0xA0
|
#define JUMP_CHILD 0xA0
|
||||||
|
@ -640,7 +646,6 @@ void nsexec(void)
|
||||||
case JUMP_PARENT:{
|
case JUMP_PARENT:{
|
||||||
int len;
|
int len;
|
||||||
pid_t child, first_child = -1;
|
pid_t child, first_child = -1;
|
||||||
char buf[JSON_MAX];
|
|
||||||
bool ready = false;
|
bool ready = false;
|
||||||
|
|
||||||
/* For debugging. */
|
/* For debugging. */
|
||||||
|
@ -716,6 +721,18 @@ void nsexec(void)
|
||||||
kill(child, SIGKILL);
|
kill(child, SIGKILL);
|
||||||
bail("failed to sync with child: write(SYNC_RECVPID_ACK)");
|
bail("failed to sync with child: write(SYNC_RECVPID_ACK)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Send the init_func pid back to our parent.
|
||||||
|
*
|
||||||
|
* Send the init_func pid and the pid of the first child back to our parent.
|
||||||
|
* We need to send both back because we can't reap the first child we created (CLONE_PARENT).
|
||||||
|
* It becomes the responsibility of our parent to reap the first child.
|
||||||
|
*/
|
||||||
|
len = dprintf(pipenum, "{\"pid\": %d, \"pid_first\": %d}\n", child, first_child);
|
||||||
|
if (len < 0) {
|
||||||
|
kill(child, SIGKILL);
|
||||||
|
bail("unable to generate JSON for child pid");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SYNC_CHILD_READY:
|
case SYNC_CHILD_READY:
|
||||||
|
@ -759,23 +776,6 @@ void nsexec(void)
|
||||||
bail("unexpected sync value: %u", s);
|
bail("unexpected sync value: %u", s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the init_func pid and the pid of the first child back to our parent.
|
|
||||||
*
|
|
||||||
* We need to send both back because we can't reap the first child we created (CLONE_PARENT).
|
|
||||||
* It becomes the responsibility of our parent to reap the first child.
|
|
||||||
*/
|
|
||||||
len = snprintf(buf, JSON_MAX, "{\"pid\": %d, \"pid_first\": %d}\n", child, first_child);
|
|
||||||
if (len < 0) {
|
|
||||||
kill(child, SIGKILL);
|
|
||||||
bail("unable to generate JSON for child pid");
|
|
||||||
}
|
|
||||||
if (write(pipenum, buf, len) != len) {
|
|
||||||
kill(child, SIGKILL);
|
|
||||||
bail("unable to send child pid to bootstrapper");
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,14 +862,17 @@ void nsexec(void)
|
||||||
if (setresuid(0, 0, 0) < 0)
|
if (setresuid(0, 0, 0) < 0)
|
||||||
bail("failed to become root in user namespace");
|
bail("failed to become root in user namespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unshare all of the namespaces. Note that we don't merge this
|
* Unshare all of the namespaces. Now, it should be noted that this
|
||||||
* with clone() because there were some old kernel versions where
|
* ordering might break in the future (especially with rootless
|
||||||
* clone(CLONE_PARENT | CLONE_NEWPID) was broken, so we'll just do
|
* containers). But for now, it's not possible to split this into
|
||||||
* it the long way.
|
* CLONE_NEWUSER + [the rest] because of some RHEL SELinux issues.
|
||||||
|
*
|
||||||
|
* Note that we don't merge this with clone() because there were
|
||||||
|
* some old kernel versions where clone(CLONE_PARENT | CLONE_NEWPID)
|
||||||
|
* was broken, so we'll just do it the long way anyway.
|
||||||
*/
|
*/
|
||||||
if (unshare(config.cloneflags) < 0)
|
if (unshare(config.cloneflags & ~CLONE_NEWCGROUP) < 0)
|
||||||
bail("failed to unshare namespaces");
|
bail("failed to unshare namespaces");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -958,6 +961,18 @@ void nsexec(void)
|
||||||
bail("setgroups failed");
|
bail("setgroups failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ... wait until our topmost parent has finished cgroup setup in p.manager.Apply() ... */
|
||||||
|
if (config.cloneflags & CLONE_NEWCGROUP) {
|
||||||
|
uint8_t value;
|
||||||
|
if (read(pipenum, &value, sizeof(value)) != sizeof(value))
|
||||||
|
bail("read synchronisation value failed");
|
||||||
|
if (value == CREATECGROUPNS) {
|
||||||
|
if (unshare(CLONE_NEWCGROUP) < 0)
|
||||||
|
bail("failed to unshare cgroup namespace");
|
||||||
|
} else
|
||||||
|
bail("received unknown synchronisation value");
|
||||||
|
}
|
||||||
|
|
||||||
s = SYNC_CHILD_READY;
|
s = SYNC_CHILD_READY;
|
||||||
if (write(syncfd, &s, sizeof(s)) != sizeof(s))
|
if (write(syncfd, &s, sizeof(s)) != sizeof(s))
|
||||||
bail("failed to sync with patent: write(SYNC_CHILD_READY)");
|
bail("failed to sync with patent: write(SYNC_CHILD_READY)");
|
||||||
|
|
|
@ -5,6 +5,7 @@ package user
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
@ -115,22 +116,23 @@ func CurrentGroup() (Group, error) {
|
||||||
return LookupGid(unix.Getgid())
|
return LookupGid(unix.Getgid())
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentUserSubUIDs() ([]SubID, error) {
|
func currentUserSubIDs(fileName string) ([]SubID, error) {
|
||||||
u, err := CurrentUser()
|
u, err := CurrentUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ParseSubIDFileFilter("/etc/subuid",
|
filter := func(entry SubID) bool {
|
||||||
func(entry SubID) bool { return entry.Name == u.Name })
|
return entry.Name == u.Name || entry.Name == strconv.Itoa(u.Uid)
|
||||||
|
}
|
||||||
|
return ParseSubIDFileFilter(fileName, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentGroupSubGIDs() ([]SubID, error) {
|
func CurrentUserSubUIDs() ([]SubID, error) {
|
||||||
g, err := CurrentGroup()
|
return currentUserSubIDs("/etc/subuid")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return ParseSubIDFileFilter("/etc/subgid",
|
|
||||||
func(entry SubID) bool { return entry.Name == g.Name })
|
func CurrentUserSubGIDs() ([]SubID, error) {
|
||||||
|
return currentUserSubIDs("/etc/subgid")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentProcessUIDMap() ([]IDMap, error) {
|
func CurrentProcessUIDMap() ([]IDMap, error) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# OCI runtime-spec. When updating this, make sure you use a version tag rather
|
# OCI runtime-spec. When updating this, make sure you use a version tag rather
|
||||||
# than a commit ID so it's much more obvious what version of the spec we are
|
# than a commit ID so it's much more obvious what version of the spec we are
|
||||||
# using.
|
# using.
|
||||||
github.com/opencontainers/runtime-spec v1.0.0
|
github.com/opencontainers/runtime-spec 5684b8af48c1ac3b1451fa499724e30e3c20a294
|
||||||
# Core libcontainer functionality.
|
# Core libcontainer functionality.
|
||||||
github.com/mrunalp/fileutils ed869b029674c0e9ce4c0dfa781405c2d9946d08
|
github.com/mrunalp/fileutils ed869b029674c0e9ce4c0dfa781405c2d9946d08
|
||||||
github.com/opencontainers/selinux v1.0.0-rc1
|
github.com/opencontainers/selinux v1.0.0-rc1
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
|
the standard library logger.
|
||||||
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
|
|
||||||
many large deployments. The core API is unlikely to change much but please
|
|
||||||
version control your Logrus to make sure you aren't fetching latest `master` on
|
|
||||||
every build.**
|
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** Unfortunately, the author failed to
|
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||||
realize the consequences of renaming to lower-case. Due to the Go package
|
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||||
environment, this caused issues. Regretfully, there's no turning back now.
|
this caused issues in the community and we needed a standard. Some environments
|
||||||
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
Everything using `logrus` will need to use the lower-case:
|
Everything using `logrus` will need to use the lower-case:
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
I am terribly sorry for this inconvenience. Logrus strives hard for backwards
|
To fix Glide, see [these
|
||||||
compatibility, and the author failed to realize the cascading consequences of
|
|
||||||
such a name-change. To fix Glide, see [these
|
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||||
|
For an in-depth explanation of the casing issue, see [this
|
||||||
|
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||||
|
|
||||||
|
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
||||||
|
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
||||||
|
needs. If you'd like to help, please reach out to me at `simon at author's
|
||||||
|
username dot com`.
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
plain text):
|
plain text):
|
||||||
|
@ -266,6 +268,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
||||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||||
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||||
|
| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) |
|
||||||
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||||
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||||
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||||
|
@ -280,7 +283,7 @@ Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/v
|
||||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||||
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||||
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||||
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
||||||
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||||
|
@ -369,6 +372,7 @@ The built-in logging formatters are:
|
||||||
|
|
||||||
Third party logging formatters:
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can by parsed by Kubernetes and Google Container Engine.
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||||
|
@ -449,13 +453,13 @@ Logrus has a built in facility for asserting the presence of log messages. This
|
||||||
```go
|
```go
|
||||||
import(
|
import(
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/sirupsen/logrus/hooks/null"
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSomething(t*testing.T){
|
func TestSomething(t*testing.T){
|
||||||
logger, hook := null.NewNullLogger()
|
logger, hook := test.NewNullLogger()
|
||||||
logger.Error("Helloerror")
|
logger.Error("Helloerror")
|
||||||
|
|
||||||
assert.Equal(t, 1, len(hook.Entries))
|
assert.Equal(t, 1, len(hook.Entries))
|
||||||
|
|
|
@ -35,6 +35,7 @@ type Entry struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
|
|
@ -31,7 +31,7 @@ func SetFormatter(formatter Formatter) {
|
||||||
func SetLevel(level Level) {
|
func SetLevel(level Level) {
|
||||||
std.mu.Lock()
|
std.mu.Lock()
|
||||||
defer std.mu.Unlock()
|
defer std.mu.Unlock()
|
||||||
std.setLevel(level)
|
std.SetLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
// GetLevel returns the standard logger level.
|
||||||
|
|
|
@ -2,7 +2,7 @@ package logrus
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const DefaultTimestampFormat = time.RFC3339
|
const defaultTimestampFormat = time.RFC3339
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type fieldKey string
|
type fieldKey string
|
||||||
|
|
||||||
|
// FieldMap allows customization of the key names for default fields.
|
||||||
type FieldMap map[fieldKey]string
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
// Default key names for the default fields
|
||||||
const (
|
const (
|
||||||
FieldKeyMsg = "msg"
|
FieldKeyMsg = "msg"
|
||||||
FieldKeyLevel = "level"
|
FieldKeyLevel = "level"
|
||||||
|
@ -22,6 +25,7 @@ func (f FieldMap) resolve(key fieldKey) string {
|
||||||
return string(key)
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSONFormatter formats logs into parsable json
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
|
@ -29,7 +33,7 @@ type JSONFormatter struct {
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
DisableTimestamp bool
|
DisableTimestamp bool
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for various fields.
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
// As an example:
|
// As an example:
|
||||||
// formatter := &JSONFormatter{
|
// formatter := &JSONFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
|
@ -41,6 +45,7 @@ type JSONFormatter struct {
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+3)
|
data := make(Fields, len(entry.Data)+3)
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
|
@ -57,7 +62,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
if !f.DisableTimestamp {
|
||||||
|
|
|
@ -25,7 +25,7 @@ type Logger struct {
|
||||||
Formatter Formatter
|
Formatter Formatter
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged. `logrus.Debug` is useful in
|
// logged.
|
||||||
Level Level
|
Level Level
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
mu MutexWrap
|
mu MutexWrap
|
||||||
|
@ -312,6 +312,6 @@ func (logger *Logger) level() Level {
|
||||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) setLevel(level Level) {
|
func (logger *Logger) SetLevel(level Level) {
|
||||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal(f io.Writer) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import "syscall"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TCGETS
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
|
||||||
type Termios syscall.Termios
|
type Termios unix.Termios
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal(f io.Writer) bool {
|
|
||||||
var termios Termios
|
|
||||||
switch v := f.(type) {
|
|
||||||
case *os.File:
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
// +build solaris,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(f io.Writer) bool {
|
|
||||||
switch v := f.(type) {
|
|
||||||
case *os.File:
|
|
||||||
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows,!appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
enableProcessedOutput = 0x0001
|
|
||||||
enableWrapAtEolOutput = 0x0002
|
|
||||||
enableVirtualTerminalProcessing = 0x0004
|
|
||||||
)
|
|
||||||
|
|
||||||
func getVersion() (float64, error) {
|
|
||||||
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
|
|
||||||
cmd := exec.Command("cmd", "ver")
|
|
||||||
cmd.Stdout = stdout
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The output should be like "Microsoft Windows [Version XX.X.XXXXXX]"
|
|
||||||
version := strings.Replace(stdout.String(), "\n", "", -1)
|
|
||||||
version = strings.Replace(version, "\r\n", "", -1)
|
|
||||||
|
|
||||||
x1 := strings.Index(version, "[Version")
|
|
||||||
|
|
||||||
if x1 == -1 || strings.Index(version, "]") == -1 {
|
|
||||||
return -1, errors.New("Can't determine Windows version")
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.ParseFloat(version[x1+9:x1+13], 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ver, err := getVersion()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate Virtual Processing for Windows CMD
|
|
||||||
// Info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
|
||||||
if ver >= 10 {
|
|
||||||
handle := syscall.Handle(os.Stderr.Fd())
|
|
||||||
procSetConsoleMode.Call(uintptr(handle), enableProcessedOutput|enableWrapAtEolOutput|enableVirtualTerminalProcessing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal(f io.Writer) bool {
|
|
||||||
switch v := f.(type) {
|
|
||||||
case *os.File:
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,14 @@ package logrus
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -14,7 +18,7 @@ const (
|
||||||
red = 31
|
red = 31
|
||||||
green = 32
|
green = 32
|
||||||
yellow = 33
|
yellow = 33
|
||||||
blue = 34
|
blue = 36
|
||||||
gray = 37
|
gray = 37
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,6 +30,7 @@ func init() {
|
||||||
baseTimestamp = time.Now()
|
baseTimestamp = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextFormatter formats logs into text
|
||||||
type TextFormatter struct {
|
type TextFormatter struct {
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
ForceColors bool
|
ForceColors bool
|
||||||
|
@ -52,10 +57,6 @@ type TextFormatter struct {
|
||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
QuoteEmptyFields bool
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
// QuoteCharacter can be set to the override the default quoting character "
|
|
||||||
// with something else. For example: ', or `.
|
|
||||||
QuoteCharacter string
|
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
// Whether the logger's out is to a terminal
|
||||||
isTerminal bool
|
isTerminal bool
|
||||||
|
|
||||||
|
@ -63,14 +64,21 @@ type TextFormatter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
func (f *TextFormatter) init(entry *Entry) {
|
||||||
if len(f.QuoteCharacter) == 0 {
|
|
||||||
f.QuoteCharacter = "\""
|
|
||||||
}
|
|
||||||
if entry.Logger != nil {
|
if entry.Logger != nil {
|
||||||
f.isTerminal = IsTerminal(entry.Logger.Out)
|
f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) checkIfTerminal(w io.Writer) bool {
|
||||||
|
switch v := w.(type) {
|
||||||
|
case *os.File:
|
||||||
|
return terminal.IsTerminal(int(v.Fd()))
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
var b *bytes.Buffer
|
var b *bytes.Buffer
|
||||||
keys := make([]string, 0, len(entry.Data))
|
keys := make([]string, 0, len(entry.Data))
|
||||||
|
@ -95,7 +103,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
timestampFormat = DefaultTimestampFormat
|
timestampFormat = defaultTimestampFormat
|
||||||
}
|
}
|
||||||
if isColored {
|
if isColored {
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
f.printColored(b, entry, keys, timestampFormat)
|
||||||
|
@ -153,7 +161,7 @@ func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
(ch >= '0' && ch <= '9') ||
|
(ch >= '0' && ch <= '9') ||
|
||||||
ch == '-' || ch == '.') {
|
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,29 +169,23 @@ func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
b.WriteString(key)
|
b.WriteString(key)
|
||||||
b.WriteByte('=')
|
b.WriteByte('=')
|
||||||
f.appendValue(b, value)
|
f.appendValue(b, value)
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||||
switch value := value.(type) {
|
stringVal, ok := value.(string)
|
||||||
case string:
|
if !ok {
|
||||||
if !f.needsQuoting(value) {
|
stringVal = fmt.Sprint(value)
|
||||||
b.WriteString(value)
|
}
|
||||||
|
|
||||||
|
if !f.needsQuoting(stringVal) {
|
||||||
|
b.WriteString(stringVal)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter)
|
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||||
}
|
|
||||||
case error:
|
|
||||||
errmsg := value.Error()
|
|
||||||
if !f.needsQuoting(errmsg) {
|
|
||||||
b.WriteString(errmsg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Fprint(b, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,951 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeCodes contains escape sequences that can be written to the terminal in
|
||||||
|
// order to achieve different styles of text.
|
||||||
|
type EscapeCodes struct {
|
||||||
|
// Foreground colors
|
||||||
|
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
||||||
|
|
||||||
|
// Reset all attributes
|
||||||
|
Reset []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var vt100EscapeCodes = EscapeCodes{
|
||||||
|
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
||||||
|
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
||||||
|
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
||||||
|
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
||||||
|
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
||||||
|
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
||||||
|
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
||||||
|
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
||||||
|
|
||||||
|
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||||
|
// reading lines of input.
|
||||||
|
type Terminal struct {
|
||||||
|
// AutoCompleteCallback, if non-null, is called for each keypress with
|
||||||
|
// the full input line and the current position of the cursor (in
|
||||||
|
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||||
|
// press is processed normally. Otherwise it returns a replacement line
|
||||||
|
// and the new cursor position.
|
||||||
|
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||||
|
|
||||||
|
// Escape contains a pointer to the escape codes for this terminal.
|
||||||
|
// It's always a valid pointer, although the escape codes themselves
|
||||||
|
// may be empty if the terminal doesn't support them.
|
||||||
|
Escape *EscapeCodes
|
||||||
|
|
||||||
|
// lock protects the terminal and the state in this object from
|
||||||
|
// concurrent processing of a key press and a Write() call.
|
||||||
|
lock sync.Mutex
|
||||||
|
|
||||||
|
c io.ReadWriter
|
||||||
|
prompt []rune
|
||||||
|
|
||||||
|
// line is the current line being entered.
|
||||||
|
line []rune
|
||||||
|
// pos is the logical position of the cursor in line
|
||||||
|
pos int
|
||||||
|
// echo is true if local echo is enabled
|
||||||
|
echo bool
|
||||||
|
// pasteActive is true iff there is a bracketed paste operation in
|
||||||
|
// progress.
|
||||||
|
pasteActive bool
|
||||||
|
|
||||||
|
// cursorX contains the current X value of the cursor where the left
|
||||||
|
// edge is 0. cursorY contains the row number where the first row of
|
||||||
|
// the current line is 0.
|
||||||
|
cursorX, cursorY int
|
||||||
|
// maxLine is the greatest value of cursorY so far.
|
||||||
|
maxLine int
|
||||||
|
|
||||||
|
termWidth, termHeight int
|
||||||
|
|
||||||
|
// outBuf contains the terminal data to be sent.
|
||||||
|
outBuf []byte
|
||||||
|
// remainder contains the remainder of any partial key sequences after
|
||||||
|
// a read. It aliases into inBuf.
|
||||||
|
remainder []byte
|
||||||
|
inBuf [256]byte
|
||||||
|
|
||||||
|
// history contains previously entered commands so that they can be
|
||||||
|
// accessed with the up and down keys.
|
||||||
|
history stRingBuffer
|
||||||
|
// historyIndex stores the currently accessed history entry, where zero
|
||||||
|
// means the immediately previous entry.
|
||||||
|
historyIndex int
|
||||||
|
// When navigating up and down the history it's possible to return to
|
||||||
|
// the incomplete, initial line. That value is stored in
|
||||||
|
// historyPending.
|
||||||
|
historyPending string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
||||||
|
// a local terminal, that terminal must first have been put into raw mode.
|
||||||
|
// prompt is a string that is written at the start of each input line (i.e.
|
||||||
|
// "> ").
|
||||||
|
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
||||||
|
return &Terminal{
|
||||||
|
Escape: &vt100EscapeCodes,
|
||||||
|
c: c,
|
||||||
|
prompt: []rune(prompt),
|
||||||
|
termWidth: 80,
|
||||||
|
termHeight: 24,
|
||||||
|
echo: true,
|
||||||
|
historyIndex: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyCtrlD = 4
|
||||||
|
keyCtrlU = 21
|
||||||
|
keyEnter = '\r'
|
||||||
|
keyEscape = 27
|
||||||
|
keyBackspace = 127
|
||||||
|
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||||
|
keyUp
|
||||||
|
keyDown
|
||||||
|
keyLeft
|
||||||
|
keyRight
|
||||||
|
keyAltLeft
|
||||||
|
keyAltRight
|
||||||
|
keyHome
|
||||||
|
keyEnd
|
||||||
|
keyDeleteWord
|
||||||
|
keyDeleteLine
|
||||||
|
keyClearScreen
|
||||||
|
keyPasteStart
|
||||||
|
keyPasteEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
crlf = []byte{'\r', '\n'}
|
||||||
|
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||||
|
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||||
|
)
|
||||||
|
|
||||||
|
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||||
|
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||||
|
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return utf8.RuneError, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive {
|
||||||
|
switch b[0] {
|
||||||
|
case 1: // ^A
|
||||||
|
return keyHome, b[1:]
|
||||||
|
case 5: // ^E
|
||||||
|
return keyEnd, b[1:]
|
||||||
|
case 8: // ^H
|
||||||
|
return keyBackspace, b[1:]
|
||||||
|
case 11: // ^K
|
||||||
|
return keyDeleteLine, b[1:]
|
||||||
|
case 12: // ^L
|
||||||
|
return keyClearScreen, b[1:]
|
||||||
|
case 23: // ^W
|
||||||
|
return keyDeleteWord, b[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] != keyEscape {
|
||||||
|
if !utf8.FullRune(b) {
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
r, l := utf8.DecodeRune(b)
|
||||||
|
return r, b[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||||
|
switch b[2] {
|
||||||
|
case 'A':
|
||||||
|
return keyUp, b[3:]
|
||||||
|
case 'B':
|
||||||
|
return keyDown, b[3:]
|
||||||
|
case 'C':
|
||||||
|
return keyRight, b[3:]
|
||||||
|
case 'D':
|
||||||
|
return keyLeft, b[3:]
|
||||||
|
case 'H':
|
||||||
|
return keyHome, b[3:]
|
||||||
|
case 'F':
|
||||||
|
return keyEnd, b[3:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
|
switch b[5] {
|
||||||
|
case 'C':
|
||||||
|
return keyAltRight, b[6:]
|
||||||
|
case 'D':
|
||||||
|
return keyAltLeft, b[6:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
||||||
|
return keyPasteStart, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
||||||
|
return keyPasteEnd, b[6:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then we have a key that we don't recognise, or a
|
||||||
|
// partial sequence. It's not clear how one should find the end of a
|
||||||
|
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
||||||
|
// appears at the end of a sequence.
|
||||||
|
for i, c := range b[0:] {
|
||||||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
||||||
|
return keyUnknown, b[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8.RuneError, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue appends data to the end of t.outBuf
|
||||||
|
func (t *Terminal) queue(data []rune) {
|
||||||
|
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
||||||
|
var space = []rune{' '}
|
||||||
|
|
||||||
|
func isPrintable(key rune) bool {
|
||||||
|
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||||
|
return key >= 32 && !isInSurrogateArea
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
||||||
|
// given, logical position in the text.
|
||||||
|
func (t *Terminal) moveCursorToPos(pos int) {
|
||||||
|
if !t.echo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
x := visualLength(t.prompt) + pos
|
||||||
|
y := x / t.termWidth
|
||||||
|
x = x % t.termWidth
|
||||||
|
|
||||||
|
up := 0
|
||||||
|
if y < t.cursorY {
|
||||||
|
up = t.cursorY - y
|
||||||
|
}
|
||||||
|
|
||||||
|
down := 0
|
||||||
|
if y > t.cursorY {
|
||||||
|
down = y - t.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
left := 0
|
||||||
|
if x < t.cursorX {
|
||||||
|
left = t.cursorX - x
|
||||||
|
}
|
||||||
|
|
||||||
|
right := 0
|
||||||
|
if x > t.cursorX {
|
||||||
|
right = x - t.cursorX
|
||||||
|
}
|
||||||
|
|
||||||
|
t.cursorX = x
|
||||||
|
t.cursorY = y
|
||||||
|
t.move(up, down, left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) move(up, down, left, right int) {
|
||||||
|
movement := make([]rune, 3*(up+down+left+right))
|
||||||
|
m := movement
|
||||||
|
for i := 0; i < up; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'A'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < down; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'B'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'D'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
for i := 0; i < right; i++ {
|
||||||
|
m[0] = keyEscape
|
||||||
|
m[1] = '['
|
||||||
|
m[2] = 'C'
|
||||||
|
m = m[3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
t.queue(movement)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearLineToRight() {
|
||||||
|
op := []rune{keyEscape, '[', 'K'}
|
||||||
|
t.queue(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLineLength = 4096
|
||||||
|
|
||||||
|
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
||||||
|
if t.echo {
|
||||||
|
t.moveCursorToPos(0)
|
||||||
|
t.writeLine(newLine)
|
||||||
|
for i := len(newLine); i < len(t.line); i++ {
|
||||||
|
t.writeLine(space)
|
||||||
|
}
|
||||||
|
t.moveCursorToPos(newPos)
|
||||||
|
}
|
||||||
|
t.line = newLine
|
||||||
|
t.pos = newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) advanceCursor(places int) {
|
||||||
|
t.cursorX += places
|
||||||
|
t.cursorY += t.cursorX / t.termWidth
|
||||||
|
if t.cursorY > t.maxLine {
|
||||||
|
t.maxLine = t.cursorY
|
||||||
|
}
|
||||||
|
t.cursorX = t.cursorX % t.termWidth
|
||||||
|
|
||||||
|
if places > 0 && t.cursorX == 0 {
|
||||||
|
// Normally terminals will advance the current position
|
||||||
|
// when writing a character. But that doesn't happen
|
||||||
|
// for the last character in a line. However, when
|
||||||
|
// writing a character (except a new line) that causes
|
||||||
|
// a line wrap, the position will be advanced two
|
||||||
|
// places.
|
||||||
|
//
|
||||||
|
// So, if we are stopping at the end of a line, we
|
||||||
|
// need to write a newline so that our cursor can be
|
||||||
|
// advanced to the next line.
|
||||||
|
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) eraseNPreviousChars(n int) {
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.pos < n {
|
||||||
|
n = t.pos
|
||||||
|
}
|
||||||
|
t.pos -= n
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
copy(t.line[t.pos:], t.line[n+t.pos:])
|
||||||
|
t.line = t.line[:len(t.line)-n]
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
t.queue(space)
|
||||||
|
}
|
||||||
|
t.advanceCursor(n)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToLeftWord returns then number of characters from the cursor to the
|
||||||
|
// start of the previous word.
|
||||||
|
func (t *Terminal) countToLeftWord() int {
|
||||||
|
if t.pos == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := t.pos - 1
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
for pos > 0 {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos--
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.pos - pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// countToRightWord returns then number of characters from the cursor to the
|
||||||
|
// start of the next word.
|
||||||
|
func (t *Terminal) countToRightWord() int {
|
||||||
|
pos := t.pos
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] == ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
for pos < len(t.line) {
|
||||||
|
if t.line[pos] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
return pos - t.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// visualLength returns the number of visible glyphs in s.
|
||||||
|
func visualLength(runes []rune) int {
|
||||||
|
inEscapeSeq := false
|
||||||
|
length := 0
|
||||||
|
|
||||||
|
for _, r := range runes {
|
||||||
|
switch {
|
||||||
|
case inEscapeSeq:
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
||||||
|
inEscapeSeq = false
|
||||||
|
}
|
||||||
|
case r == '\x1b':
|
||||||
|
inEscapeSeq = true
|
||||||
|
default:
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleKey processes the given key and, optionally, returns a line of text
|
||||||
|
// that the user has entered.
|
||||||
|
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||||
|
if t.pasteActive && key != keyEnter {
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case keyBackspace:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
case keyAltLeft:
|
||||||
|
// move left by a word.
|
||||||
|
t.pos -= t.countToLeftWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyAltRight:
|
||||||
|
// move right by a word.
|
||||||
|
t.pos += t.countToRightWord()
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyLeft:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos--
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyRight:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyHome:
|
||||||
|
if t.pos == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = 0
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyEnd:
|
||||||
|
if t.pos == len(t.line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.pos = len(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyUp:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if t.historyIndex == -1 {
|
||||||
|
t.historyPending = string(t.line)
|
||||||
|
}
|
||||||
|
t.historyIndex++
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
case keyDown:
|
||||||
|
switch t.historyIndex {
|
||||||
|
case -1:
|
||||||
|
return
|
||||||
|
case 0:
|
||||||
|
runes := []rune(t.historyPending)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
t.historyIndex--
|
||||||
|
default:
|
||||||
|
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
||||||
|
if ok {
|
||||||
|
t.historyIndex--
|
||||||
|
runes := []rune(entry)
|
||||||
|
t.setLine(runes, len(runes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case keyEnter:
|
||||||
|
t.moveCursorToPos(len(t.line))
|
||||||
|
t.queue([]rune("\r\n"))
|
||||||
|
line = string(t.line)
|
||||||
|
ok = true
|
||||||
|
t.line = t.line[:0]
|
||||||
|
t.pos = 0
|
||||||
|
t.cursorX = 0
|
||||||
|
t.cursorY = 0
|
||||||
|
t.maxLine = 0
|
||||||
|
case keyDeleteWord:
|
||||||
|
// Delete zero or more spaces and then one or more characters.
|
||||||
|
t.eraseNPreviousChars(t.countToLeftWord())
|
||||||
|
case keyDeleteLine:
|
||||||
|
// Delete everything from the current cursor position to the
|
||||||
|
// end of line.
|
||||||
|
for i := t.pos; i < len(t.line); i++ {
|
||||||
|
t.queue(space)
|
||||||
|
t.advanceCursor(1)
|
||||||
|
}
|
||||||
|
t.line = t.line[:t.pos]
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
case keyCtrlD:
|
||||||
|
// Erase the character under the current position.
|
||||||
|
// The EOF case when the line is empty is handled in
|
||||||
|
// readLine().
|
||||||
|
if t.pos < len(t.line) {
|
||||||
|
t.pos++
|
||||||
|
t.eraseNPreviousChars(1)
|
||||||
|
}
|
||||||
|
case keyCtrlU:
|
||||||
|
t.eraseNPreviousChars(t.pos)
|
||||||
|
case keyClearScreen:
|
||||||
|
// Erases the screen and moves the cursor to the home position.
|
||||||
|
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.setLine(t.line, t.pos)
|
||||||
|
default:
|
||||||
|
if t.AutoCompleteCallback != nil {
|
||||||
|
prefix := string(t.line[:t.pos])
|
||||||
|
suffix := string(t.line[t.pos:])
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if completeOk {
|
||||||
|
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isPrintable(key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(t.line) == maxLineLength {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.addKeyToLine(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKeyToLine inserts the given key at the current position in the current
|
||||||
|
// line.
|
||||||
|
func (t *Terminal) addKeyToLine(key rune) {
|
||||||
|
if len(t.line) == cap(t.line) {
|
||||||
|
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
||||||
|
copy(newLine, t.line)
|
||||||
|
t.line = newLine
|
||||||
|
}
|
||||||
|
t.line = t.line[:len(t.line)+1]
|
||||||
|
copy(t.line[t.pos+1:], t.line[t.pos:])
|
||||||
|
t.line[t.pos] = key
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line[t.pos:])
|
||||||
|
}
|
||||||
|
t.pos++
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) writeLine(line []rune) {
|
||||||
|
for len(line) != 0 {
|
||||||
|
remainingOnLine := t.termWidth - t.cursorX
|
||||||
|
todo := len(line)
|
||||||
|
if todo > remainingOnLine {
|
||||||
|
todo = remainingOnLine
|
||||||
|
}
|
||||||
|
t.queue(line[:todo])
|
||||||
|
t.advanceCursor(visualLength(line[:todo]))
|
||||||
|
line = line[todo:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||||
|
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||||
|
for len(buf) > 0 {
|
||||||
|
i := bytes.IndexByte(buf, '\n')
|
||||||
|
todo := len(buf)
|
||||||
|
if i >= 0 {
|
||||||
|
todo = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var nn int
|
||||||
|
nn, err = w.Write(buf[:todo])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
buf = buf[todo:]
|
||||||
|
|
||||||
|
if i >= 0 {
|
||||||
|
if _, err = w.Write(crlf); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
buf = buf[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
// This is the easy case: there's nothing on the screen that we
|
||||||
|
// have to move out of the way.
|
||||||
|
return writeWithCRLF(t.c, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a prompt and possibly user input on the screen. We
|
||||||
|
// have to clear it first.
|
||||||
|
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
||||||
|
t.cursorX = 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
|
||||||
|
for t.cursorY > 0 {
|
||||||
|
t.move(1 /* up */, 0, 0, 0)
|
||||||
|
t.cursorY--
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
|
||||||
|
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
if t.echo {
|
||||||
|
t.writeLine(t.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
|
||||||
|
if _, err = t.c.Write(t.outBuf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||||
|
// echo, from the terminal.
|
||||||
|
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
oldPrompt := t.prompt
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
t.echo = false
|
||||||
|
|
||||||
|
line, err = t.readLine()
|
||||||
|
|
||||||
|
t.prompt = oldPrompt
|
||||||
|
t.echo = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine returns a line of input from the terminal.
|
||||||
|
func (t *Terminal) ReadLine() (line string, err error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
return t.readLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) readLine() (line string, err error) {
|
||||||
|
// t.lock must be held at this point
|
||||||
|
|
||||||
|
if t.cursorX == 0 && t.cursorY == 0 {
|
||||||
|
t.writeLine(t.prompt)
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
lineIsPasted := t.pasteActive
|
||||||
|
|
||||||
|
for {
|
||||||
|
rest := t.remainder
|
||||||
|
lineOk := false
|
||||||
|
for !lineOk {
|
||||||
|
var key rune
|
||||||
|
key, rest = bytesToKey(rest, t.pasteActive)
|
||||||
|
if key == utf8.RuneError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
if key == keyCtrlD {
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == keyPasteStart {
|
||||||
|
t.pasteActive = true
|
||||||
|
if len(t.line) == 0 {
|
||||||
|
lineIsPasted = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if key == keyPasteEnd {
|
||||||
|
t.pasteActive = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !t.pasteActive {
|
||||||
|
lineIsPasted = false
|
||||||
|
}
|
||||||
|
line, lineOk = t.handleKey(key)
|
||||||
|
}
|
||||||
|
if len(rest) > 0 {
|
||||||
|
n := copy(t.inBuf[:], rest)
|
||||||
|
t.remainder = t.inBuf[:n]
|
||||||
|
} else {
|
||||||
|
t.remainder = nil
|
||||||
|
}
|
||||||
|
t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
if lineOk {
|
||||||
|
if t.echo {
|
||||||
|
t.historyIndex = -1
|
||||||
|
t.history.Add(line)
|
||||||
|
}
|
||||||
|
if lineIsPasted {
|
||||||
|
err = ErrPasteIndicator
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.remainder is a slice at the beginning of t.inBuf
|
||||||
|
// containing a partial key sequence
|
||||||
|
readBuf := t.inBuf[len(t.remainder):]
|
||||||
|
var n int
|
||||||
|
|
||||||
|
t.lock.Unlock()
|
||||||
|
n, err = t.c.Read(readBuf)
|
||||||
|
t.lock.Lock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||||
|
func (t *Terminal) SetPrompt(prompt string) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
t.prompt = []rune(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
||||||
|
// Move cursor to column zero at the start of the line.
|
||||||
|
t.move(t.cursorY, 0, t.cursorX, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
t.clearLineToRight()
|
||||||
|
for t.cursorY < numPrevLines {
|
||||||
|
// Move down a line
|
||||||
|
t.move(0, 1, 0, 0)
|
||||||
|
t.cursorY++
|
||||||
|
t.clearLineToRight()
|
||||||
|
}
|
||||||
|
// Move back to beginning.
|
||||||
|
t.move(t.cursorY, 0, 0, 0)
|
||||||
|
t.cursorX, t.cursorY = 0, 0
|
||||||
|
|
||||||
|
t.queue(t.prompt)
|
||||||
|
t.advanceCursor(visualLength(t.prompt))
|
||||||
|
t.writeLine(t.line)
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) SetSize(width, height int) error {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
if width == 0 {
|
||||||
|
width = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
oldWidth := t.termWidth
|
||||||
|
t.termWidth, t.termHeight = width, height
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case width == oldWidth:
|
||||||
|
// If the width didn't change then nothing else needs to be
|
||||||
|
// done.
|
||||||
|
return nil
|
||||||
|
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
||||||
|
// If there is nothing on current line and no prompt printed,
|
||||||
|
// just do nothing
|
||||||
|
return nil
|
||||||
|
case width < oldWidth:
|
||||||
|
// Some terminals (e.g. xterm) will truncate lines that were
|
||||||
|
// too long when shinking. Others, (e.g. gnome-terminal) will
|
||||||
|
// attempt to wrap them. For the former, repainting t.maxLine
|
||||||
|
// works great, but that behaviour goes badly wrong in the case
|
||||||
|
// of the latter because they have doubled every full line.
|
||||||
|
|
||||||
|
// We assume that we are working on a terminal that wraps lines
|
||||||
|
// and adjust the cursor position based on every previous line
|
||||||
|
// wrapping and turning into two. This causes the prompt on
|
||||||
|
// xterms to move upwards, which isn't great, but it avoids a
|
||||||
|
// huge mess with gnome-terminal.
|
||||||
|
if t.cursorX >= t.termWidth {
|
||||||
|
t.cursorX = t.termWidth - 1
|
||||||
|
}
|
||||||
|
t.cursorY *= 2
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
||||||
|
case width > oldWidth:
|
||||||
|
// If the terminal expands then our position calculations will
|
||||||
|
// be wrong in the future because we think the cursor is
|
||||||
|
// |t.pos| chars into the string, but there will be a gap at
|
||||||
|
// the end of any wrapped line.
|
||||||
|
//
|
||||||
|
// But the position will actually be correct until we move, so
|
||||||
|
// we can move back to the beginning and repaint everything.
|
||||||
|
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := t.c.Write(t.outBuf)
|
||||||
|
t.outBuf = t.outBuf[:0]
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type pasteIndicatorError struct{}
|
||||||
|
|
||||||
|
func (pasteIndicatorError) Error() string {
|
||||||
|
return "terminal: ErrPasteIndicator not correctly handled"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
||||||
|
// to valid line data. It indicates that bracketed paste mode is enabled and
|
||||||
|
// that the returned line consists only of pasted data. Programs may wish to
|
||||||
|
// interpret pasted data more literally than typed data.
|
||||||
|
var ErrPasteIndicator = pasteIndicatorError{}
|
||||||
|
|
||||||
|
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
||||||
|
// with markers. Not all terminals support this but, if it is supported, then
|
||||||
|
// enabling this mode will stop any autocomplete callback from running due to
|
||||||
|
// pastes. Additionally, any lines that are completely pasted will be returned
|
||||||
|
// from ReadLine with the error set to ErrPasteIndicator.
|
||||||
|
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
||||||
|
if on {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004h")
|
||||||
|
} else {
|
||||||
|
io.WriteString(t.c, "\x1b[?2004l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stRingBuffer is a ring buffer of strings.
|
||||||
|
type stRingBuffer struct {
|
||||||
|
// entries contains max elements.
|
||||||
|
entries []string
|
||||||
|
max int
|
||||||
|
// head contains the index of the element most recently added to the ring.
|
||||||
|
head int
|
||||||
|
// size contains the number of elements in the ring.
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stRingBuffer) Add(a string) {
|
||||||
|
if s.entries == nil {
|
||||||
|
const defaultNumEntries = 100
|
||||||
|
s.entries = make([]string, defaultNumEntries)
|
||||||
|
s.max = defaultNumEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
s.head = (s.head + 1) % s.max
|
||||||
|
s.entries[s.head] = a
|
||||||
|
if s.size < s.max {
|
||||||
|
s.size++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
||||||
|
// If n is zero then the immediately prior value is returned, if one, then the
|
||||||
|
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||||
|
// false.
|
||||||
|
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
||||||
|
if n >= s.size {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
index := s.head - n
|
||||||
|
if index < 0 {
|
||||||
|
index += s.max
|
||||||
|
}
|
||||||
|
return s.entries[index], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||||
|
// The slice returned does not include the \n.
|
||||||
|
// readPasswordLine also ignores any \r it finds.
|
||||||
|
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||||
|
var buf [1]byte
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := reader.Read(buf[:])
|
||||||
|
if n > 0 {
|
||||||
|
switch buf[0] {
|
||||||
|
case '\n':
|
||||||
|
return ret, nil
|
||||||
|
case '\r':
|
||||||
|
// remove \r from passwords on Windows
|
||||||
|
default:
|
||||||
|
ret = append(ret, buf[0])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF && len(ret) > 0 {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldState := State{termios: *termios}
|
||||||
|
|
||||||
|
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||||
|
// the termios(3) manpage.
|
||||||
|
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||||
|
termios.Oflag &^= unix.OPOST
|
||||||
|
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||||
|
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||||
|
termios.Cflag |= unix.CS8
|
||||||
|
termios.Cc[unix.VMIN] = 1
|
||||||
|
termios.Cc[unix.VTIME] = 0
|
||||||
|
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &State{termios: *termios}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return int(ws.Col), int(ws.Row), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||||
|
type passwordReader int
|
||||||
|
|
||||||
|
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||||
|
return unix.Read(int(r), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := *termios
|
||||||
|
newState.Lflag &^= unix.ECHO
|
||||||
|
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||||
|
newState.Iflag |= unix.ICRNL
|
||||||
|
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
|
||||||
|
|
||||||
|
return readPasswordLine(passwordReader(fd))
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
const ioctlWriteTermios = unix.TIOCSETA
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
const ioctlWriteTermios = unix.TCSETS
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct{}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
termios unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||||
|
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldState := *val
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||||
|
|
||||||
|
var buf [16]byte
|
||||||
|
var ret []byte
|
||||||
|
for {
|
||||||
|
n, err := syscall.Read(fd, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if buf[n-1] == '\n' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
ret = append(ret, buf[:n]...)
|
||||||
|
if n < len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldState := State{termios: *termios}
|
||||||
|
|
||||||
|
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||||
|
termios.Oflag &^= unix.OPOST
|
||||||
|
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||||
|
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||||
|
termios.Cflag |= unix.CS8
|
||||||
|
termios.Cc[unix.VMIN] = 1
|
||||||
|
termios.Cc[unix.VTIME] = 0
|
||||||
|
|
||||||
|
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, oldState *State) error {
|
||||||
|
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &State{termios: *termios}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return int(ws.Col), int(ws.Row), nil
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := terminal.MakeRaw(0)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer terminal.Restore(0, oldState)
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) bool {
|
||||||
|
var st uint32
|
||||||
|
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
|
// restore the terminal after a signal.
|
||||||
|
func GetState(fd int) (*State, error) {
|
||||||
|
var st uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &State{st}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, state *State) error {
|
||||||
|
return windows.SetConsoleMode(windows.Handle(fd), state.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var info windows.ConsoleScreenBufferInfo
|
||||||
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return int(info.Size.X), int(info.Size.Y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||||
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
|
// returned does not include the \n.
|
||||||
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
|
var st uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
old := st
|
||||||
|
|
||||||
|
st &^= (windows.ENABLE_ECHO_INPUT)
|
||||||
|
st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer windows.SetConsoleMode(windows.Handle(fd), old)
|
||||||
|
|
||||||
|
var h windows.Handle
|
||||||
|
p, _ := windows.GetCurrentProcess()
|
||||||
|
if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := os.NewFile(uintptr(h), "stdin")
|
||||||
|
defer f.Close()
|
||||||
|
return readPasswordLine(f)
|
||||||
|
}
|
Loading…
Reference in New Issue