Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -115,12 +115,12 @@ github.com/googleapis/gax-go v2.0.0 |
| 115 | 115 |
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9 |
| 116 | 116 |
|
| 117 | 117 |
# containerd |
| 118 |
-github.com/containerd/containerd v1.2.0-beta.2 |
|
| 118 |
+github.com/containerd/containerd d97a907f7f781c0ab8340877d8e6b53cc7f1c2f6 |
|
| 119 | 119 |
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c |
| 120 |
-github.com/containerd/continuity c7c5070e6f6e090ab93b0a61eb921f2196fc3383 |
|
| 120 |
+github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537 |
|
| 121 | 121 |
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 |
| 122 | 122 |
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 |
| 123 |
-github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031 |
|
| 123 |
+github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 |
|
| 124 | 124 |
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 |
| 125 | 125 |
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d |
| 126 | 126 |
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef |
| ... | ... |
@@ -2,6 +2,7 @@ |
| 2 | 2 |
|
| 3 | 3 |
[](https://godoc.org/github.com/containerd/containerd) |
| 4 | 4 |
[](https://travis-ci.org/containerd/containerd) |
| 5 |
+[](https://ci.appveyor.com/project/mlaventure/containerd-3g73f?branch=master) |
|
| 5 | 6 |
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield) |
| 6 | 7 |
[](https://goreportcard.com/report/github.com/containerd/containerd) |
| 7 | 8 |
[](https://bestpractices.coreinfrastructure.org/projects/1271) |
| ... | ... |
@@ -223,7 +224,7 @@ This will be the best place to discuss design and implementation. |
| 223 | 223 |
|
| 224 | 224 |
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development. |
| 225 | 225 |
|
| 226 |
-**Slack:** https://dockr.ly/community |
|
| 226 |
+**Slack:** https://join.slack.com/t/dockercommunity/shared_invite/enQtNDM4NjAwNDMyOTUwLWZlMDZmYWRjZjk4Zjc5ZGQ5NWZkOWI1Yjk2NGE3ZWVlYjYxM2VhYjczOWIyZDFhZTE3NTUwZWQzMjhmNGYyZTg |
|
| 227 | 227 |
|
| 228 | 228 |
### Reporting security issues |
| 229 | 229 |
|
| ... | ... |
@@ -141,7 +141,7 @@ type EventsClient interface {
|
| 141 | 141 |
// Forward sends an event that has already been packaged into an envelope |
| 142 | 142 |
// with a timestamp and namespace. |
| 143 | 143 |
// |
| 144 |
- // This is useful if earlier timestamping is required or when fowarding on |
|
| 144 |
+ // This is useful if earlier timestamping is required or when forwarding on |
|
| 145 | 145 |
// behalf of another component, namespace or publisher. |
| 146 | 146 |
Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*google_protobuf2.Empty, error) |
| 147 | 147 |
// Subscribe to a stream of events, possibly returning only that match any |
| ... | ... |
@@ -223,7 +223,7 @@ type EventsServer interface {
|
| 223 | 223 |
// Forward sends an event that has already been packaged into an envelope |
| 224 | 224 |
// with a timestamp and namespace. |
| 225 | 225 |
// |
| 226 |
- // This is useful if earlier timestamping is required or when fowarding on |
|
| 226 |
+ // This is useful if earlier timestamping is required or when forwarding on |
|
| 227 | 227 |
// behalf of another component, namespace or publisher. |
| 228 | 228 |
Forward(context.Context, *ForwardRequest) (*google_protobuf2.Empty, error) |
| 229 | 229 |
// Subscribe to a stream of events, possibly returning only that match any |
| ... | ... |
@@ -20,7 +20,7 @@ service Events {
|
| 20 | 20 |
// Forward sends an event that has already been packaged into an envelope |
| 21 | 21 |
// with a timestamp and namespace. |
| 22 | 22 |
// |
| 23 |
- // This is useful if earlier timestamping is required or when fowarding on |
|
| 23 |
+ // This is useful if earlier timestamping is required or when forwarding on |
|
| 24 | 24 |
// behalf of another component, namespace or publisher. |
| 25 | 25 |
rpc Forward(ForwardRequest) returns (google.protobuf.Empty); |
| 26 | 26 |
|
| ... | ... |
@@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
|
| 141 | 141 |
if err != nil {
|
| 142 | 142 |
return nil, err |
| 143 | 143 |
} |
| 144 |
+ if streams.Stdin == nil {
|
|
| 145 |
+ fifos.Stdin = "" |
|
| 146 |
+ } |
|
| 147 |
+ if streams.Stdout == nil {
|
|
| 148 |
+ fifos.Stdout = "" |
|
| 149 |
+ } |
|
| 150 |
+ if streams.Stderr == nil {
|
|
| 151 |
+ fifos.Stderr = "" |
|
| 152 |
+ } |
|
| 144 | 153 |
return copyIO(fifos, streams) |
| 145 | 154 |
} |
| 146 | 155 |
} |
| ... | ... |
@@ -20,25 +20,21 @@ package containerd |
| 20 | 20 |
|
| 21 | 21 |
import ( |
| 22 | 22 |
"context" |
| 23 |
- "encoding/json" |
|
| 24 | 23 |
"fmt" |
| 25 | 24 |
"os" |
| 26 | 25 |
"path/filepath" |
| 27 | 26 |
"syscall" |
| 28 | 27 |
|
| 29 |
- "github.com/containerd/containerd/api/types" |
|
| 30 | 28 |
"github.com/containerd/containerd/containers" |
| 31 | 29 |
"github.com/containerd/containerd/content" |
| 32 | 30 |
"github.com/containerd/containerd/errdefs" |
| 33 | 31 |
"github.com/containerd/containerd/images" |
| 34 | 32 |
"github.com/containerd/containerd/mount" |
| 35 | 33 |
"github.com/containerd/containerd/platforms" |
| 36 |
- "github.com/containerd/containerd/runtime/linux/runctypes" |
|
| 37 | 34 |
"github.com/gogo/protobuf/proto" |
| 38 | 35 |
protobuf "github.com/gogo/protobuf/types" |
| 39 | 36 |
"github.com/opencontainers/image-spec/identity" |
| 40 | 37 |
"github.com/opencontainers/image-spec/specs-go/v1" |
| 41 |
- ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 42 | 38 |
"github.com/pkg/errors" |
| 43 | 39 |
) |
| 44 | 40 |
|
| ... | ... |
@@ -105,44 +101,6 @@ func WithCheckpoint(im Image, snapshotKey string) NewContainerOpts {
|
| 105 | 105 |
} |
| 106 | 106 |
} |
| 107 | 107 |
|
| 108 |
-// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a |
|
| 109 |
-// previous checkpoint. Additional software such as CRIU may be required to |
|
| 110 |
-// restore a task from a checkpoint |
|
| 111 |
-func WithTaskCheckpoint(im Image) NewTaskOpts {
|
|
| 112 |
- return func(ctx context.Context, c *Client, info *TaskInfo) error {
|
|
| 113 |
- desc := im.Target() |
|
| 114 |
- id := desc.Digest |
|
| 115 |
- index, err := decodeIndex(ctx, c.ContentStore(), desc) |
|
| 116 |
- if err != nil {
|
|
| 117 |
- return err |
|
| 118 |
- } |
|
| 119 |
- for _, m := range index.Manifests {
|
|
| 120 |
- if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
|
| 121 |
- info.Checkpoint = &types.Descriptor{
|
|
| 122 |
- MediaType: m.MediaType, |
|
| 123 |
- Size_: m.Size, |
|
| 124 |
- Digest: m.Digest, |
|
| 125 |
- } |
|
| 126 |
- return nil |
|
| 127 |
- } |
|
| 128 |
- } |
|
| 129 |
- return fmt.Errorf("checkpoint not found in index %s", id)
|
|
| 130 |
- } |
|
| 131 |
-} |
|
| 132 |
- |
|
| 133 |
-func decodeIndex(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (*v1.Index, error) {
|
|
| 134 |
- var index v1.Index |
|
| 135 |
- p, err := content.ReadBlob(ctx, store, desc) |
|
| 136 |
- if err != nil {
|
|
| 137 |
- return nil, err |
|
| 138 |
- } |
|
| 139 |
- if err := json.Unmarshal(p, &index); err != nil {
|
|
| 140 |
- return nil, err |
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- return &index, nil |
|
| 144 |
-} |
|
| 145 |
- |
|
| 146 | 108 |
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the |
| 147 | 109 |
// filesystem to be used by a container with user namespaces |
| 148 | 110 |
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
|
| ... | ... |
@@ -221,19 +179,3 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
|
| 221 | 221 |
return os.Lchown(path, u, g) |
| 222 | 222 |
} |
| 223 | 223 |
} |
| 224 |
- |
|
| 225 |
-// WithNoPivotRoot instructs the runtime not to you pivot_root |
|
| 226 |
-func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
|
|
| 227 |
- if info.Options == nil {
|
|
| 228 |
- info.Options = &runctypes.CreateOptions{
|
|
| 229 |
- NoPivotRoot: true, |
|
| 230 |
- } |
|
| 231 |
- return nil |
|
| 232 |
- } |
|
| 233 |
- copts, ok := info.Options.(*runctypes.CreateOptions) |
|
| 234 |
- if !ok {
|
|
| 235 |
- return errors.New("invalid options type, expected runctypes.CreateOptions")
|
|
| 236 |
- } |
|
| 237 |
- copts.NoPivotRoot = true |
|
| 238 |
- return nil |
|
| 239 |
-} |
| ... | ... |
@@ -33,6 +33,8 @@ import ( |
| 33 | 33 |
"github.com/containerd/containerd/errdefs" |
| 34 | 34 |
"github.com/containerd/containerd/filters" |
| 35 | 35 |
"github.com/containerd/containerd/log" |
| 36 |
+ |
|
| 37 |
+ "github.com/containerd/continuity" |
|
| 36 | 38 |
digest "github.com/opencontainers/go-digest" |
| 37 | 39 |
ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 38 | 40 |
"github.com/pkg/errors" |
| ... | ... |
@@ -651,5 +653,5 @@ func writeTimestampFile(p string, t time.Time) error {
|
| 651 | 651 |
return err |
| 652 | 652 |
} |
| 653 | 653 |
|
| 654 |
- return ioutil.WriteFile(p, b, 0666) |
|
| 654 |
+ return continuity.AtomicWriteFile(p, b, 0666) |
|
| 655 | 655 |
} |
| ... | ... |
@@ -132,11 +132,11 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest, |
| 132 | 132 |
// clean up!! |
| 133 | 133 |
defer os.RemoveAll(w.path) |
| 134 | 134 |
|
| 135 |
+ if _, err := os.Stat(target); err == nil {
|
|
| 136 |
+ // collision with the target file! |
|
| 137 |
+ return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst) |
|
| 138 |
+ } |
|
| 135 | 139 |
if err := os.Rename(ingest, target); err != nil {
|
| 136 |
- if os.IsExist(err) {
|
|
| 137 |
- // collision with the target file! |
|
| 138 |
- return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", dgst) |
|
| 139 |
- } |
|
| 140 | 140 |
return err |
| 141 | 141 |
} |
| 142 | 142 |
commitTime := time.Now() |
| ... | ... |
@@ -30,7 +30,7 @@ import ( |
| 30 | 30 |
) |
| 31 | 31 |
|
| 32 | 32 |
// WithProfile receives the name of a file stored on disk comprising a json |
| 33 |
-// formated seccomp profile, as specified by the opencontainers/runtime-spec. |
|
| 33 |
+// formatted seccomp profile, as specified by the opencontainers/runtime-spec. |
|
| 34 | 34 |
// The profile is read from the file, unmarshaled, and set to the spec. |
| 35 | 35 |
func WithProfile(profile string) oci.SpecOpts {
|
| 36 | 36 |
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
|
| ... | ... |
@@ -52,7 +52,7 @@ var _ events.Subscriber = &Exchange{}
|
| 52 | 52 |
|
| 53 | 53 |
// Forward accepts an envelope to be direcly distributed on the exchange. |
| 54 | 54 |
// |
| 55 |
-// This is useful when an event is forwaded on behalf of another namespace or |
|
| 55 |
+// This is useful when an event is forwarded on behalf of another namespace or |
|
| 56 | 56 |
// when the event is propagated on behalf of another publisher. |
| 57 | 57 |
func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) {
|
| 58 | 58 |
if err := validateEnvelope(envelope); err != nil {
|
| 59 | 59 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 0 |
+/* |
|
| 1 |
+ Copyright The containerd Authors. |
|
| 2 |
+ |
|
| 3 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 4 |
+ you may not use this file except in compliance with the License. |
|
| 5 |
+ You may obtain a copy of the License at |
|
| 6 |
+ |
|
| 7 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 8 |
+ |
|
| 9 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 10 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 11 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 12 |
+ See the License for the specific language governing permissions and |
|
| 13 |
+ limitations under the License. |
|
| 14 |
+*/ |
|
| 15 |
+ |
|
| 16 |
+package containerd |
|
| 17 |
+ |
|
| 18 |
+import ( |
|
| 19 |
+ "context" |
|
| 20 |
+ "io" |
|
| 21 |
+ |
|
| 22 |
+ "github.com/containerd/containerd/images" |
|
| 23 |
+ ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 24 |
+) |
|
| 25 |
+ |
|
| 26 |
+type exportOpts struct {
|
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// ExportOpt allows the caller to specify export-specific options |
|
| 30 |
+type ExportOpt func(c *exportOpts) error |
|
| 31 |
+ |
|
| 32 |
+func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
|
| 33 |
+ var eopts exportOpts |
|
| 34 |
+ for _, o := range opts {
|
|
| 35 |
+ if err := o(&eopts); err != nil {
|
|
| 36 |
+ return eopts, err |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ return eopts, nil |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// Export exports an image to a Tar stream. |
|
| 43 |
+// OCI format is used by default. |
|
| 44 |
+// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. |
|
| 45 |
+// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. |
|
| 46 |
+func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
|
| 47 |
+ _, err := resolveExportOpt(opts...) // unused now |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return nil, err |
|
| 50 |
+ } |
|
| 51 |
+ pr, pw := io.Pipe() |
|
| 52 |
+ go func() {
|
|
| 53 |
+ pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw)) |
|
| 54 |
+ }() |
|
| 55 |
+ return pr, nil |
|
| 56 |
+} |
| ... | ... |
@@ -22,7 +22,6 @@ import ( |
| 22 | 22 |
|
| 23 | 23 |
"github.com/containerd/containerd/errdefs" |
| 24 | 24 |
"github.com/containerd/containerd/images" |
| 25 |
- ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 26 | 25 |
) |
| 27 | 26 |
|
| 28 | 27 |
type importOpts struct {
|
| ... | ... |
@@ -84,35 +83,3 @@ func (c *Client) Import(ctx context.Context, importer images.Importer, reader io |
| 84 | 84 |
} |
| 85 | 85 |
return images, nil |
| 86 | 86 |
} |
| 87 |
- |
|
| 88 |
-type exportOpts struct {
|
|
| 89 |
-} |
|
| 90 |
- |
|
| 91 |
-// ExportOpt allows the caller to specify export-specific options |
|
| 92 |
-type ExportOpt func(c *exportOpts) error |
|
| 93 |
- |
|
| 94 |
-func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
|
| 95 |
- var eopts exportOpts |
|
| 96 |
- for _, o := range opts {
|
|
| 97 |
- if err := o(&eopts); err != nil {
|
|
| 98 |
- return eopts, err |
|
| 99 |
- } |
|
| 100 |
- } |
|
| 101 |
- return eopts, nil |
|
| 102 |
-} |
|
| 103 |
- |
|
| 104 |
-// Export exports an image to a Tar stream. |
|
| 105 |
-// OCI format is used by default. |
|
| 106 |
-// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc. |
|
| 107 |
-// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream. |
|
| 108 |
-func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
|
| 109 |
- _, err := resolveExportOpt(opts...) // unused now |
|
| 110 |
- if err != nil {
|
|
| 111 |
- return nil, err |
|
| 112 |
- } |
|
| 113 |
- pr, pw := io.Pipe() |
|
| 114 |
- go func() {
|
|
| 115 |
- pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw)) |
|
| 116 |
- }() |
|
| 117 |
- return pr, nil |
|
| 118 |
-} |
| ... | ... |
@@ -33,25 +33,14 @@ import ( |
| 33 | 33 |
|
| 34 | 34 |
// Install a binary image into the opt service |
| 35 | 35 |
func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error {
|
| 36 |
- resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
|
|
| 37 |
- Filters: []string{
|
|
| 38 |
- "id==opt", |
|
| 39 |
- }, |
|
| 40 |
- }) |
|
| 41 |
- if err != nil {
|
|
| 42 |
- return err |
|
| 43 |
- } |
|
| 44 |
- if len(resp.Plugins) != 1 {
|
|
| 45 |
- return errors.New("opt service not enabled")
|
|
| 46 |
- } |
|
| 47 |
- path := resp.Plugins[0].Exports["path"] |
|
| 48 |
- if path == "" {
|
|
| 49 |
- return errors.New("opt path not exported")
|
|
| 50 |
- } |
|
| 51 | 36 |
var config InstallConfig |
| 52 | 37 |
for _, o := range opts {
|
| 53 | 38 |
o(&config) |
| 54 | 39 |
} |
| 40 |
+ path, err := c.getInstallPath(ctx, config) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return err |
|
| 43 |
+ } |
|
| 55 | 44 |
var ( |
| 56 | 45 |
cs = image.ContentStore() |
| 57 | 46 |
platform = platforms.Default() |
| ... | ... |
@@ -89,3 +78,25 @@ func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) |
| 89 | 89 |
} |
| 90 | 90 |
return nil |
| 91 | 91 |
} |
| 92 |
+ |
|
| 93 |
+func (c *Client) getInstallPath(ctx context.Context, config InstallConfig) (string, error) {
|
|
| 94 |
+ if config.Path != "" {
|
|
| 95 |
+ return config.Path, nil |
|
| 96 |
+ } |
|
| 97 |
+ resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{
|
|
| 98 |
+ Filters: []string{
|
|
| 99 |
+ "id==opt", |
|
| 100 |
+ }, |
|
| 101 |
+ }) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return "", err |
|
| 104 |
+ } |
|
| 105 |
+ if len(resp.Plugins) != 1 {
|
|
| 106 |
+ return "", errors.New("opt service not enabled")
|
|
| 107 |
+ } |
|
| 108 |
+ path := resp.Plugins[0].Exports["path"] |
|
| 109 |
+ if path == "" {
|
|
| 110 |
+ return "", errors.New("opt path not exported")
|
|
| 111 |
+ } |
|
| 112 |
+ return path, nil |
|
| 113 |
+} |
| ... | ... |
@@ -25,6 +25,8 @@ type InstallConfig struct {
|
| 25 | 25 |
Libs bool |
| 26 | 26 |
// Replace will overwrite existing binaries or libs in the opt directory |
| 27 | 27 |
Replace bool |
| 28 |
+ // Path to install libs and binaries to |
|
| 29 |
+ Path string |
|
| 28 | 30 |
} |
| 29 | 31 |
|
| 30 | 32 |
// WithInstallLibs installs libs from the image |
| ... | ... |
@@ -36,3 +38,10 @@ func WithInstallLibs(c *InstallConfig) {
|
| 36 | 36 |
func WithInstallReplace(c *InstallConfig) {
|
| 37 | 37 |
c.Replace = true |
| 38 | 38 |
} |
| 39 |
+ |
|
| 40 |
+// WithInstallPath sets the optional install path |
|
| 41 |
+func WithInstallPath(path string) InstallOpts {
|
|
| 42 |
+ return func(c *InstallConfig) {
|
|
| 43 |
+ c.Path = path |
|
| 44 |
+ } |
|
| 45 |
+} |
| ... | ... |
@@ -17,11 +17,11 @@ |
| 17 | 17 |
package metadata |
| 18 | 18 |
|
| 19 | 19 |
import ( |
| 20 |
- "github.com/boltdb/bolt" |
|
| 21 | 20 |
digest "github.com/opencontainers/go-digest" |
| 21 |
+ bolt "go.etcd.io/bbolt" |
|
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 |
-// The layout where a "/" delineates a bucket is desribed in the following |
|
| 24 |
+// The layout where a "/" delineates a bucket is described in the following |
|
| 25 | 25 |
// section. Please try to follow this as closely as possible when adding |
| 26 | 26 |
// functionality. We can bolster this with helpers and more structure if that |
| 27 | 27 |
// becomes an issue. |
| ... | ... |
@@ -164,11 +164,11 @@ func getSnapshotterBucket(tx *bolt.Tx, namespace, snapshotter string) *bolt.Buck |
| 164 | 164 |
} |
| 165 | 165 |
|
| 166 | 166 |
func createBlobBucket(tx *bolt.Tx, namespace string, dgst digest.Digest) (*bolt.Bucket, error) {
|
| 167 |
- bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(dgst.String())) |
|
| 167 |
+ bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectBlob) |
|
| 168 | 168 |
if err != nil {
|
| 169 | 169 |
return nil, err |
| 170 | 170 |
} |
| 171 |
- return bkt, nil |
|
| 171 |
+ return bkt.CreateBucket([]byte(dgst.String())) |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 | 174 |
func getBlobsBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
|
| ... | ... |
@@ -21,7 +21,6 @@ import ( |
| 21 | 21 |
"strings" |
| 22 | 22 |
"time" |
| 23 | 23 |
|
| 24 |
- "github.com/boltdb/bolt" |
|
| 25 | 24 |
"github.com/containerd/containerd/containers" |
| 26 | 25 |
"github.com/containerd/containerd/errdefs" |
| 27 | 26 |
"github.com/containerd/containerd/filters" |
| ... | ... |
@@ -32,6 +31,7 @@ import ( |
| 32 | 32 |
"github.com/gogo/protobuf/proto" |
| 33 | 33 |
"github.com/gogo/protobuf/types" |
| 34 | 34 |
"github.com/pkg/errors" |
| 35 |
+ bolt "go.etcd.io/bbolt" |
|
| 35 | 36 |
) |
| 36 | 37 |
|
| 37 | 38 |
type containerStore struct {
|
| ... | ... |
@@ -23,7 +23,6 @@ import ( |
| 23 | 23 |
"sync" |
| 24 | 24 |
"time" |
| 25 | 25 |
|
| 26 |
- "github.com/boltdb/bolt" |
|
| 27 | 26 |
"github.com/containerd/containerd/content" |
| 28 | 27 |
"github.com/containerd/containerd/errdefs" |
| 29 | 28 |
"github.com/containerd/containerd/filters" |
| ... | ... |
@@ -34,6 +33,7 @@ import ( |
| 34 | 34 |
digest "github.com/opencontainers/go-digest" |
| 35 | 35 |
ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 36 | 36 |
"github.com/pkg/errors" |
| 37 |
+ bolt "go.etcd.io/bbolt" |
|
| 37 | 38 |
) |
| 38 | 39 |
|
| 39 | 40 |
type contentStore struct {
|
| ... | ... |
@@ -592,9 +592,6 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, |
| 592 | 592 |
} |
| 593 | 593 |
size = nw.desc.Size |
| 594 | 594 |
actual = nw.desc.Digest |
| 595 |
- if getBlobBucket(tx, nw.namespace, actual) != nil {
|
|
| 596 |
- return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) |
|
| 597 |
- } |
|
| 598 | 595 |
} else {
|
| 599 | 596 |
status, err := nw.w.Status() |
| 600 | 597 |
if err != nil {
|
| ... | ... |
@@ -606,18 +603,16 @@ func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, |
| 606 | 606 |
size = status.Offset |
| 607 | 607 |
actual = nw.w.Digest() |
| 608 | 608 |
|
| 609 |
- if err := nw.w.Commit(ctx, size, expected); err != nil {
|
|
| 610 |
- if !errdefs.IsAlreadyExists(err) {
|
|
| 611 |
- return "", err |
|
| 612 |
- } |
|
| 613 |
- if getBlobBucket(tx, nw.namespace, actual) != nil {
|
|
| 614 |
- return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) |
|
| 615 |
- } |
|
| 609 |
+ if err := nw.w.Commit(ctx, size, expected); err != nil && !errdefs.IsAlreadyExists(err) {
|
|
| 610 |
+ return "", err |
|
| 616 | 611 |
} |
| 617 | 612 |
} |
| 618 | 613 |
|
| 619 | 614 |
bkt, err := createBlobBucket(tx, nw.namespace, actual) |
| 620 | 615 |
if err != nil {
|
| 616 |
+ if err == bolt.ErrBucketExists {
|
|
| 617 |
+ return "", errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) |
|
| 618 |
+ } |
|
| 621 | 619 |
return "", err |
| 622 | 620 |
} |
| 623 | 621 |
|
| ... | ... |
@@ -23,12 +23,12 @@ import ( |
| 23 | 23 |
"sync" |
| 24 | 24 |
"time" |
| 25 | 25 |
|
| 26 |
- "github.com/boltdb/bolt" |
|
| 27 | 26 |
"github.com/containerd/containerd/content" |
| 28 | 27 |
"github.com/containerd/containerd/gc" |
| 29 | 28 |
"github.com/containerd/containerd/log" |
| 30 | 29 |
"github.com/containerd/containerd/snapshots" |
| 31 | 30 |
"github.com/pkg/errors" |
| 31 |
+ bolt "go.etcd.io/bbolt" |
|
| 32 | 32 |
) |
| 33 | 33 |
|
| 34 | 34 |
const ( |
| ... | ... |
@@ -43,7 +43,7 @@ const ( |
| 43 | 43 |
// dbVersion represents updates to the schema |
| 44 | 44 |
// version which are additions and compatible with |
| 45 | 45 |
// prior version of the same schema. |
| 46 |
- dbVersion = 2 |
|
| 46 |
+ dbVersion = 3 |
|
| 47 | 47 |
) |
| 48 | 48 |
|
| 49 | 49 |
// DB represents a metadata database backed by a bolt |
| ... | ... |
@@ -23,7 +23,6 @@ import ( |
| 23 | 23 |
"strings" |
| 24 | 24 |
"time" |
| 25 | 25 |
|
| 26 |
- "github.com/boltdb/bolt" |
|
| 27 | 26 |
"github.com/containerd/containerd/errdefs" |
| 28 | 27 |
"github.com/containerd/containerd/filters" |
| 29 | 28 |
"github.com/containerd/containerd/images" |
| ... | ... |
@@ -33,6 +32,7 @@ import ( |
| 33 | 33 |
digest "github.com/opencontainers/go-digest" |
| 34 | 34 |
ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 35 | 35 |
"github.com/pkg/errors" |
| 36 |
+ bolt "go.etcd.io/bbolt" |
|
| 36 | 37 |
) |
| 37 | 38 |
|
| 38 | 39 |
type imageStore struct {
|
| ... | ... |
@@ -20,7 +20,6 @@ import ( |
| 20 | 20 |
"context" |
| 21 | 21 |
"time" |
| 22 | 22 |
|
| 23 |
- "github.com/boltdb/bolt" |
|
| 24 | 23 |
"github.com/containerd/containerd/errdefs" |
| 25 | 24 |
"github.com/containerd/containerd/filters" |
| 26 | 25 |
"github.com/containerd/containerd/leases" |
| ... | ... |
@@ -28,6 +27,7 @@ import ( |
| 28 | 28 |
"github.com/containerd/containerd/namespaces" |
| 29 | 29 |
digest "github.com/opencontainers/go-digest" |
| 30 | 30 |
"github.com/pkg/errors" |
| 31 |
+ bolt "go.etcd.io/bbolt" |
|
| 31 | 32 |
) |
| 32 | 33 |
|
| 33 | 34 |
// LeaseManager manages the create/delete lifecyle of leases |
| ... | ... |
@@ -16,7 +16,7 @@ |
| 16 | 16 |
|
| 17 | 17 |
package metadata |
| 18 | 18 |
|
| 19 |
-import "github.com/boltdb/bolt" |
|
| 19 |
+import bolt "go.etcd.io/bbolt" |
|
| 20 | 20 |
|
| 21 | 21 |
type migration struct {
|
| 22 | 22 |
schema string |
| ... | ... |
@@ -45,6 +45,11 @@ var migrations = []migration{
|
| 45 | 45 |
version: 2, |
| 46 | 46 |
migrate: migrateIngests, |
| 47 | 47 |
}, |
| 48 |
+ {
|
|
| 49 |
+ schema: "v1", |
|
| 50 |
+ version: 3, |
|
| 51 |
+ migrate: noOpMigration, |
|
| 52 |
+ }, |
|
| 48 | 53 |
} |
| 49 | 54 |
|
| 50 | 55 |
// addChildLinks Adds children key to the snapshotters to enforce snapshot |
| ... | ... |
@@ -154,3 +159,10 @@ func migrateIngests(tx *bolt.Tx) error {
|
| 154 | 154 |
|
| 155 | 155 |
return nil |
| 156 | 156 |
} |
| 157 |
+ |
|
| 158 |
+// noOpMigration was for a database change from boltdb/bolt which is no |
|
| 159 |
+// longer being supported, to go.etcd.io/bbolt which is the currently |
|
| 160 |
+// maintained repo for boltdb. |
|
| 161 |
+func noOpMigration(tx *bolt.Tx) error {
|
|
| 162 |
+ return nil |
|
| 163 |
+} |
| ... | ... |
@@ -19,11 +19,11 @@ package metadata |
| 19 | 19 |
import ( |
| 20 | 20 |
"context" |
| 21 | 21 |
|
| 22 |
- "github.com/boltdb/bolt" |
|
| 23 | 22 |
"github.com/containerd/containerd/errdefs" |
| 24 | 23 |
l "github.com/containerd/containerd/labels" |
| 25 | 24 |
"github.com/containerd/containerd/namespaces" |
| 26 | 25 |
"github.com/pkg/errors" |
| 26 |
+ bolt "go.etcd.io/bbolt" |
|
| 27 | 27 |
) |
| 28 | 28 |
|
| 29 | 29 |
type namespaceStore struct {
|
| ... | ... |
@@ -23,7 +23,6 @@ import ( |
| 23 | 23 |
"sync" |
| 24 | 24 |
"time" |
| 25 | 25 |
|
| 26 |
- "github.com/boltdb/bolt" |
|
| 27 | 26 |
"github.com/containerd/containerd/errdefs" |
| 28 | 27 |
"github.com/containerd/containerd/labels" |
| 29 | 28 |
"github.com/containerd/containerd/log" |
| ... | ... |
@@ -32,6 +31,7 @@ import ( |
| 32 | 32 |
"github.com/containerd/containerd/namespaces" |
| 33 | 33 |
"github.com/containerd/containerd/snapshots" |
| 34 | 34 |
"github.com/pkg/errors" |
| 35 |
+ bolt "go.etcd.io/bbolt" |
|
| 35 | 36 |
) |
| 36 | 37 |
|
| 37 | 38 |
type snapshotter struct {
|
| ... | ... |
@@ -32,6 +32,10 @@ var ( |
| 32 | 32 |
|
| 33 | 33 |
// Mount to the provided target |
| 34 | 34 |
func (m *Mount) Mount(target string) error {
|
| 35 |
+ if m.Type != "windows-layer" {
|
|
| 36 |
+ return errors.Errorf("invalid windows mount type: '%s'", m.Type)
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 35 | 39 |
home, layerID := filepath.Split(m.Source) |
| 36 | 40 |
|
| 37 | 41 |
parentLayerPaths, err := m.GetParentPaths() |
| ... | ... |
@@ -18,11 +18,27 @@ package oci |
| 18 | 18 |
|
| 19 | 19 |
import ( |
| 20 | 20 |
"context" |
| 21 |
+ "path/filepath" |
|
| 22 |
+ "runtime" |
|
| 23 |
+ |
|
| 24 |
+ "github.com/containerd/containerd/namespaces" |
|
| 25 |
+ "github.com/containerd/containerd/platforms" |
|
| 21 | 26 |
|
| 22 | 27 |
"github.com/containerd/containerd/containers" |
| 23 | 28 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 24 | 29 |
) |
| 25 | 30 |
|
| 31 |
+const ( |
|
| 32 |
+ rwm = "rwm" |
|
| 33 |
+ defaultRootfsPath = "rootfs" |
|
| 34 |
+) |
|
| 35 |
+ |
|
| 36 |
+var ( |
|
| 37 |
+ defaultUnixEnv = []string{
|
|
| 38 |
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
|
| 39 |
+ } |
|
| 40 |
+) |
|
| 41 |
+ |
|
| 26 | 42 |
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts |
| 27 | 43 |
// to be created without the "issues" with go vendoring and package imports |
| 28 | 44 |
type Spec = specs.Spec |
| ... | ... |
@@ -30,12 +46,36 @@ type Spec = specs.Spec |
| 30 | 30 |
// GenerateSpec will generate a default spec from the provided image |
| 31 | 31 |
// for use as a containerd container |
| 32 | 32 |
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
| 33 |
- s, err := createDefaultSpec(ctx, c.ID) |
|
| 34 |
- if err != nil {
|
|
| 33 |
+ return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...) |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// GenerateSpecWithPlatform will generate a default spec from the provided image |
|
| 37 |
+// for use as a containerd container in the platform requested. |
|
| 38 |
+func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
|
|
| 39 |
+ var s Spec |
|
| 40 |
+ if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
|
|
| 35 | 41 |
return nil, err |
| 36 | 42 |
} |
| 37 | 43 |
|
| 38 |
- return s, ApplyOpts(ctx, client, c, s, opts...) |
|
| 44 |
+ return &s, ApplyOpts(ctx, client, c, &s, opts...) |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
|
|
| 48 |
+ plat, err := platforms.Parse(platform) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ return err |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if plat.OS == "windows" {
|
|
| 54 |
+ err = populateDefaultWindowsSpec(ctx, s, id) |
|
| 55 |
+ } else {
|
|
| 56 |
+ err = populateDefaultUnixSpec(ctx, s, id) |
|
| 57 |
+ if err == nil && runtime.GOOS == "windows" {
|
|
| 58 |
+ // To run LCOW we have a Linux and Windows section. Add an empty one now. |
|
| 59 |
+ s.Windows = &specs.Windows{}
|
|
| 60 |
+ } |
|
| 61 |
+ } |
|
| 62 |
+ return err |
|
| 39 | 63 |
} |
| 40 | 64 |
|
| 41 | 65 |
// ApplyOpts applys the options to the given spec, injecting data from the |
| ... | ... |
@@ -50,7 +90,173 @@ func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *S |
| 50 | 50 |
return nil |
| 51 | 51 |
} |
| 52 | 52 |
|
| 53 |
-func createDefaultSpec(ctx context.Context, id string) (*Spec, error) {
|
|
| 54 |
- var s Spec |
|
| 55 |
- return &s, populateDefaultSpec(ctx, &s, id) |
|
| 53 |
+func defaultUnixCaps() []string {
|
|
| 54 |
+ return []string{
|
|
| 55 |
+ "CAP_CHOWN", |
|
| 56 |
+ "CAP_DAC_OVERRIDE", |
|
| 57 |
+ "CAP_FSETID", |
|
| 58 |
+ "CAP_FOWNER", |
|
| 59 |
+ "CAP_MKNOD", |
|
| 60 |
+ "CAP_NET_RAW", |
|
| 61 |
+ "CAP_SETGID", |
|
| 62 |
+ "CAP_SETUID", |
|
| 63 |
+ "CAP_SETFCAP", |
|
| 64 |
+ "CAP_SETPCAP", |
|
| 65 |
+ "CAP_NET_BIND_SERVICE", |
|
| 66 |
+ "CAP_SYS_CHROOT", |
|
| 67 |
+ "CAP_KILL", |
|
| 68 |
+ "CAP_AUDIT_WRITE", |
|
| 69 |
+ } |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func defaultUnixNamespaces() []specs.LinuxNamespace {
|
|
| 73 |
+ return []specs.LinuxNamespace{
|
|
| 74 |
+ {
|
|
| 75 |
+ Type: specs.PIDNamespace, |
|
| 76 |
+ }, |
|
| 77 |
+ {
|
|
| 78 |
+ Type: specs.IPCNamespace, |
|
| 79 |
+ }, |
|
| 80 |
+ {
|
|
| 81 |
+ Type: specs.UTSNamespace, |
|
| 82 |
+ }, |
|
| 83 |
+ {
|
|
| 84 |
+ Type: specs.MountNamespace, |
|
| 85 |
+ }, |
|
| 86 |
+ {
|
|
| 87 |
+ Type: specs.NetworkNamespace, |
|
| 88 |
+ }, |
|
| 89 |
+ } |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
|
| 93 |
+ ns, err := namespaces.NamespaceRequired(ctx) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return err |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ *s = Spec{
|
|
| 99 |
+ Version: specs.Version, |
|
| 100 |
+ Root: &specs.Root{
|
|
| 101 |
+ Path: defaultRootfsPath, |
|
| 102 |
+ }, |
|
| 103 |
+ Process: &specs.Process{
|
|
| 104 |
+ Env: defaultUnixEnv, |
|
| 105 |
+ Cwd: "/", |
|
| 106 |
+ NoNewPrivileges: true, |
|
| 107 |
+ User: specs.User{
|
|
| 108 |
+ UID: 0, |
|
| 109 |
+ GID: 0, |
|
| 110 |
+ }, |
|
| 111 |
+ Capabilities: &specs.LinuxCapabilities{
|
|
| 112 |
+ Bounding: defaultUnixCaps(), |
|
| 113 |
+ Permitted: defaultUnixCaps(), |
|
| 114 |
+ Inheritable: defaultUnixCaps(), |
|
| 115 |
+ Effective: defaultUnixCaps(), |
|
| 116 |
+ }, |
|
| 117 |
+ Rlimits: []specs.POSIXRlimit{
|
|
| 118 |
+ {
|
|
| 119 |
+ Type: "RLIMIT_NOFILE", |
|
| 120 |
+ Hard: uint64(1024), |
|
| 121 |
+ Soft: uint64(1024), |
|
| 122 |
+ }, |
|
| 123 |
+ }, |
|
| 124 |
+ }, |
|
| 125 |
+ Mounts: []specs.Mount{
|
|
| 126 |
+ {
|
|
| 127 |
+ Destination: "/proc", |
|
| 128 |
+ Type: "proc", |
|
| 129 |
+ Source: "proc", |
|
| 130 |
+ }, |
|
| 131 |
+ {
|
|
| 132 |
+ Destination: "/dev", |
|
| 133 |
+ Type: "tmpfs", |
|
| 134 |
+ Source: "tmpfs", |
|
| 135 |
+ Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
| 136 |
+ }, |
|
| 137 |
+ {
|
|
| 138 |
+ Destination: "/dev/pts", |
|
| 139 |
+ Type: "devpts", |
|
| 140 |
+ Source: "devpts", |
|
| 141 |
+ Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
|
| 142 |
+ }, |
|
| 143 |
+ {
|
|
| 144 |
+ Destination: "/dev/shm", |
|
| 145 |
+ Type: "tmpfs", |
|
| 146 |
+ Source: "shm", |
|
| 147 |
+ Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
|
| 148 |
+ }, |
|
| 149 |
+ {
|
|
| 150 |
+ Destination: "/dev/mqueue", |
|
| 151 |
+ Type: "mqueue", |
|
| 152 |
+ Source: "mqueue", |
|
| 153 |
+ Options: []string{"nosuid", "noexec", "nodev"},
|
|
| 154 |
+ }, |
|
| 155 |
+ {
|
|
| 156 |
+ Destination: "/sys", |
|
| 157 |
+ Type: "sysfs", |
|
| 158 |
+ Source: "sysfs", |
|
| 159 |
+ Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
|
| 160 |
+ }, |
|
| 161 |
+ {
|
|
| 162 |
+ Destination: "/run", |
|
| 163 |
+ Type: "tmpfs", |
|
| 164 |
+ Source: "tmpfs", |
|
| 165 |
+ Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
| 166 |
+ }, |
|
| 167 |
+ }, |
|
| 168 |
+ Linux: &specs.Linux{
|
|
| 169 |
+ MaskedPaths: []string{
|
|
| 170 |
+ "/proc/acpi", |
|
| 171 |
+ "/proc/kcore", |
|
| 172 |
+ "/proc/keys", |
|
| 173 |
+ "/proc/latency_stats", |
|
| 174 |
+ "/proc/timer_list", |
|
| 175 |
+ "/proc/timer_stats", |
|
| 176 |
+ "/proc/sched_debug", |
|
| 177 |
+ "/sys/firmware", |
|
| 178 |
+ "/proc/scsi", |
|
| 179 |
+ }, |
|
| 180 |
+ ReadonlyPaths: []string{
|
|
| 181 |
+ "/proc/asound", |
|
| 182 |
+ "/proc/bus", |
|
| 183 |
+ "/proc/fs", |
|
| 184 |
+ "/proc/irq", |
|
| 185 |
+ "/proc/sys", |
|
| 186 |
+ "/proc/sysrq-trigger", |
|
| 187 |
+ }, |
|
| 188 |
+ CgroupsPath: filepath.Join("/", ns, id),
|
|
| 189 |
+ Resources: &specs.LinuxResources{
|
|
| 190 |
+ Devices: []specs.LinuxDeviceCgroup{
|
|
| 191 |
+ {
|
|
| 192 |
+ Allow: false, |
|
| 193 |
+ Access: rwm, |
|
| 194 |
+ }, |
|
| 195 |
+ }, |
|
| 196 |
+ }, |
|
| 197 |
+ Namespaces: defaultUnixNamespaces(), |
|
| 198 |
+ }, |
|
| 199 |
+ } |
|
| 200 |
+ return nil |
|
| 201 |
+} |
|
| 202 |
+ |
|
| 203 |
+func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
|
|
| 204 |
+ *s = Spec{
|
|
| 205 |
+ Version: specs.Version, |
|
| 206 |
+ Root: &specs.Root{},
|
|
| 207 |
+ Process: &specs.Process{
|
|
| 208 |
+ Cwd: `C:\`, |
|
| 209 |
+ ConsoleSize: &specs.Box{
|
|
| 210 |
+ Width: 80, |
|
| 211 |
+ Height: 20, |
|
| 212 |
+ }, |
|
| 213 |
+ }, |
|
| 214 |
+ Windows: &specs.Windows{
|
|
| 215 |
+ IgnoreFlushesDuringBoot: true, |
|
| 216 |
+ Network: &specs.WindowsNetwork{
|
|
| 217 |
+ AllowUnqualifiedDNSQuery: true, |
|
| 218 |
+ }, |
|
| 219 |
+ }, |
|
| 220 |
+ } |
|
| 221 |
+ return nil |
|
| 56 | 222 |
} |
| ... | ... |
@@ -19,12 +19,25 @@ package oci |
| 19 | 19 |
import ( |
| 20 | 20 |
"context" |
| 21 | 21 |
"encoding/json" |
| 22 |
+ "fmt" |
|
| 22 | 23 |
"io/ioutil" |
| 24 |
+ "os" |
|
| 25 |
+ "path/filepath" |
|
| 26 |
+ "strconv" |
|
| 23 | 27 |
"strings" |
| 24 | 28 |
|
| 25 | 29 |
"github.com/containerd/containerd/containers" |
| 30 |
+ "github.com/containerd/containerd/content" |
|
| 31 |
+ "github.com/containerd/containerd/images" |
|
| 32 |
+ "github.com/containerd/containerd/mount" |
|
| 33 |
+ "github.com/containerd/containerd/namespaces" |
|
| 34 |
+ "github.com/containerd/containerd/platforms" |
|
| 35 |
+ "github.com/containerd/continuity/fs" |
|
| 36 |
+ "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 37 |
+ "github.com/opencontainers/runc/libcontainer/user" |
|
| 26 | 38 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 27 | 39 |
"github.com/pkg/errors" |
| 40 |
+ "github.com/syndtr/gocapability/capability" |
|
| 28 | 41 |
) |
| 29 | 42 |
|
| 30 | 43 |
// SpecOpts sets spec specific information to a newly generated OCI spec |
| ... | ... |
@@ -49,13 +62,45 @@ func setProcess(s *Spec) {
|
| 49 | 49 |
} |
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 |
+// setRoot sets Root to empty if unset |
|
| 53 |
+func setRoot(s *Spec) {
|
|
| 54 |
+ if s.Root == nil {
|
|
| 55 |
+ s.Root = &specs.Root{}
|
|
| 56 |
+ } |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// setLinux sets Linux to empty if unset |
|
| 60 |
+func setLinux(s *Spec) {
|
|
| 61 |
+ if s.Linux == nil {
|
|
| 62 |
+ s.Linux = &specs.Linux{}
|
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// setCapabilities sets Linux Capabilities to empty if unset |
|
| 67 |
+func setCapabilities(s *Spec) {
|
|
| 68 |
+ setProcess(s) |
|
| 69 |
+ if s.Process.Capabilities == nil {
|
|
| 70 |
+ s.Process.Capabilities = &specs.LinuxCapabilities{}
|
|
| 71 |
+ } |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 52 | 74 |
// WithDefaultSpec returns a SpecOpts that will populate the spec with default |
| 53 | 75 |
// values. |
| 54 | 76 |
// |
| 55 | 77 |
// Use as the first option to clear the spec, then apply options afterwards. |
| 56 | 78 |
func WithDefaultSpec() SpecOpts {
|
| 57 | 79 |
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
| 58 |
- return populateDefaultSpec(ctx, s, c.ID) |
|
| 80 |
+ return generateDefaultSpecWithPlatform(ctx, platforms.DefaultString(), c.ID, s) |
|
| 81 |
+ } |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+// WithDefaultSpecForPlatform returns a SpecOpts that will populate the spec |
|
| 85 |
+// with default values for a given platform. |
|
| 86 |
+// |
|
| 87 |
+// Use as the first option to clear the spec, then apply options afterwards. |
|
| 88 |
+func WithDefaultSpecForPlatform(platform string) SpecOpts {
|
|
| 89 |
+ return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
|
| 90 |
+ return generateDefaultSpecWithPlatform(ctx, platform, c.ID, s) |
|
| 59 | 91 |
} |
| 60 | 92 |
} |
| 61 | 93 |
|
| ... | ... |
@@ -81,6 +126,55 @@ func WithSpecFromFile(filename string) SpecOpts {
|
| 81 | 81 |
} |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 |
+// WithEnv appends environment variables |
|
| 85 |
+func WithEnv(environmentVariables []string) SpecOpts {
|
|
| 86 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 87 |
+ if len(environmentVariables) > 0 {
|
|
| 88 |
+ setProcess(s) |
|
| 89 |
+ s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables) |
|
| 90 |
+ } |
|
| 91 |
+ return nil |
|
| 92 |
+ } |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// replaceOrAppendEnvValues returns the defaults with the overrides either |
|
| 96 |
+// replaced by env key or appended to the list |
|
| 97 |
+func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
|
| 98 |
+ cache := make(map[string]int, len(defaults)) |
|
| 99 |
+ for i, e := range defaults {
|
|
| 100 |
+ parts := strings.SplitN(e, "=", 2) |
|
| 101 |
+ cache[parts[0]] = i |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ for _, value := range overrides {
|
|
| 105 |
+ // Values w/o = means they want this env to be removed/unset. |
|
| 106 |
+ if !strings.Contains(value, "=") {
|
|
| 107 |
+ if i, exists := cache[value]; exists {
|
|
| 108 |
+ defaults[i] = "" // Used to indicate it should be removed |
|
| 109 |
+ } |
|
| 110 |
+ continue |
|
| 111 |
+ } |
|
| 112 |
+ |
|
| 113 |
+ // Just do a normal set/update |
|
| 114 |
+ parts := strings.SplitN(value, "=", 2) |
|
| 115 |
+ if i, exists := cache[parts[0]]; exists {
|
|
| 116 |
+ defaults[i] = value |
|
| 117 |
+ } else {
|
|
| 118 |
+ defaults = append(defaults, value) |
|
| 119 |
+ } |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ // Now remove all entries that we want to "unset" |
|
| 123 |
+ for i := 0; i < len(defaults); i++ {
|
|
| 124 |
+ if defaults[i] == "" {
|
|
| 125 |
+ defaults = append(defaults[:i], defaults[i+1:]...) |
|
| 126 |
+ i-- |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ return defaults |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 84 | 133 |
// WithProcessArgs replaces the args on the generated spec |
| 85 | 134 |
func WithProcessArgs(args ...string) SpecOpts {
|
| 86 | 135 |
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
| ... | ... |
@@ -99,6 +193,32 @@ func WithProcessCwd(cwd string) SpecOpts {
|
| 99 | 99 |
} |
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 |
+// WithTTY sets the information on the spec as well as the environment variables for |
|
| 103 |
+// using a TTY |
|
| 104 |
+func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 105 |
+ setProcess(s) |
|
| 106 |
+ s.Process.Terminal = true |
|
| 107 |
+ if s.Linux != nil {
|
|
| 108 |
+ s.Process.Env = append(s.Process.Env, "TERM=xterm") |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ return nil |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// WithTTYSize sets the information on the spec as well as the environment variables for |
|
| 115 |
+// using a TTY |
|
| 116 |
+func WithTTYSize(width, height int) SpecOpts {
|
|
| 117 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 118 |
+ setProcess(s) |
|
| 119 |
+ if s.Process.ConsoleSize == nil {
|
|
| 120 |
+ s.Process.ConsoleSize = &specs.Box{}
|
|
| 121 |
+ } |
|
| 122 |
+ s.Process.ConsoleSize.Width = uint(width) |
|
| 123 |
+ s.Process.ConsoleSize.Height = uint(height) |
|
| 124 |
+ return nil |
|
| 125 |
+ } |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 102 | 128 |
// WithHostname sets the container's hostname |
| 103 | 129 |
func WithHostname(name string) SpecOpts {
|
| 104 | 130 |
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
| ... | ... |
@@ -107,59 +227,779 @@ func WithHostname(name string) SpecOpts {
|
| 107 | 107 |
} |
| 108 | 108 |
} |
| 109 | 109 |
|
| 110 |
-// WithEnv appends environment variables |
|
| 111 |
-func WithEnv(environmentVariables []string) SpecOpts {
|
|
| 110 |
+// WithMounts appends mounts |
|
| 111 |
+func WithMounts(mounts []specs.Mount) SpecOpts {
|
|
| 112 | 112 |
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
| 113 |
- if len(environmentVariables) > 0 {
|
|
| 114 |
- setProcess(s) |
|
| 115 |
- s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables) |
|
| 113 |
+ s.Mounts = append(s.Mounts, mounts...) |
|
| 114 |
+ return nil |
|
| 115 |
+ } |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+// WithHostNamespace allows a task to run inside the host's linux namespace |
|
| 119 |
+func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
|
| 120 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 121 |
+ setLinux(s) |
|
| 122 |
+ for i, n := range s.Linux.Namespaces {
|
|
| 123 |
+ if n.Type == ns {
|
|
| 124 |
+ s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) |
|
| 125 |
+ return nil |
|
| 126 |
+ } |
|
| 116 | 127 |
} |
| 117 | 128 |
return nil |
| 118 | 129 |
} |
| 119 | 130 |
} |
| 120 | 131 |
|
| 121 |
-// WithMounts appends mounts |
|
| 122 |
-func WithMounts(mounts []specs.Mount) SpecOpts {
|
|
| 132 |
+// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the |
|
| 133 |
+// spec, the existing namespace is replaced by the one provided. |
|
| 134 |
+func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
|
| 123 | 135 |
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
| 124 |
- s.Mounts = append(s.Mounts, mounts...) |
|
| 136 |
+ setLinux(s) |
|
| 137 |
+ for i, n := range s.Linux.Namespaces {
|
|
| 138 |
+ if n.Type == ns.Type {
|
|
| 139 |
+ before := s.Linux.Namespaces[:i] |
|
| 140 |
+ after := s.Linux.Namespaces[i+1:] |
|
| 141 |
+ s.Linux.Namespaces = append(before, ns) |
|
| 142 |
+ s.Linux.Namespaces = append(s.Linux.Namespaces, after...) |
|
| 143 |
+ return nil |
|
| 144 |
+ } |
|
| 145 |
+ } |
|
| 146 |
+ s.Linux.Namespaces = append(s.Linux.Namespaces, ns) |
|
| 125 | 147 |
return nil |
| 126 | 148 |
} |
| 127 | 149 |
} |
| 128 | 150 |
|
| 129 |
-// replaceOrAppendEnvValues returns the defaults with the overrides either |
|
| 130 |
-// replaced by env key or appended to the list |
|
| 131 |
-func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
|
| 132 |
- cache := make(map[string]int, len(defaults)) |
|
| 133 |
- for i, e := range defaults {
|
|
| 134 |
- parts := strings.SplitN(e, "=", 2) |
|
| 135 |
- cache[parts[0]] = i |
|
| 151 |
+// WithImageConfig configures the spec to from the configuration of an Image |
|
| 152 |
+func WithImageConfig(image Image) SpecOpts {
|
|
| 153 |
+ return WithImageConfigArgs(image, nil) |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that |
|
| 157 |
+// replaces the CMD of the image |
|
| 158 |
+func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
|
| 159 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
| 160 |
+ ic, err := image.Config(ctx) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return err |
|
| 163 |
+ } |
|
| 164 |
+ var ( |
|
| 165 |
+ ociimage v1.Image |
|
| 166 |
+ config v1.ImageConfig |
|
| 167 |
+ ) |
|
| 168 |
+ switch ic.MediaType {
|
|
| 169 |
+ case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: |
|
| 170 |
+ p, err := content.ReadBlob(ctx, image.ContentStore(), ic) |
|
| 171 |
+ if err != nil {
|
|
| 172 |
+ return err |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ if err := json.Unmarshal(p, &ociimage); err != nil {
|
|
| 176 |
+ return err |
|
| 177 |
+ } |
|
| 178 |
+ config = ociimage.Config |
|
| 179 |
+ default: |
|
| 180 |
+ return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ setProcess(s) |
|
| 184 |
+ if s.Linux != nil {
|
|
| 185 |
+ s.Process.Env = append(s.Process.Env, config.Env...) |
|
| 186 |
+ cmd := config.Cmd |
|
| 187 |
+ if len(args) > 0 {
|
|
| 188 |
+ cmd = args |
|
| 189 |
+ } |
|
| 190 |
+ s.Process.Args = append(config.Entrypoint, cmd...) |
|
| 191 |
+ |
|
| 192 |
+ cwd := config.WorkingDir |
|
| 193 |
+ if cwd == "" {
|
|
| 194 |
+ cwd = "/" |
|
| 195 |
+ } |
|
| 196 |
+ s.Process.Cwd = cwd |
|
| 197 |
+ if config.User != "" {
|
|
| 198 |
+ if err := WithUser(config.User)(ctx, client, c, s); err != nil {
|
|
| 199 |
+ return err |
|
| 200 |
+ } |
|
| 201 |
+ return WithAdditionalGIDs(fmt.Sprintf("%d", s.Process.User.UID))(ctx, client, c, s)
|
|
| 202 |
+ } |
|
| 203 |
+ // we should query the image's /etc/group for additional GIDs |
|
| 204 |
+ // even if there is no specified user in the image config |
|
| 205 |
+ return WithAdditionalGIDs("root")(ctx, client, c, s)
|
|
| 206 |
+ } else if s.Windows != nil {
|
|
| 207 |
+ s.Process.Env = config.Env |
|
| 208 |
+ s.Process.Args = append(config.Entrypoint, config.Cmd...) |
|
| 209 |
+ s.Process.User = specs.User{
|
|
| 210 |
+ Username: config.User, |
|
| 211 |
+ } |
|
| 212 |
+ } else {
|
|
| 213 |
+ return errors.New("spec does not contain Linux or Windows section")
|
|
| 214 |
+ } |
|
| 215 |
+ return nil |
|
| 136 | 216 |
} |
| 217 |
+} |
|
| 137 | 218 |
|
| 138 |
- for _, value := range overrides {
|
|
| 139 |
- // Values w/o = means they want this env to be removed/unset. |
|
| 140 |
- if !strings.Contains(value, "=") {
|
|
| 141 |
- if i, exists := cache[value]; exists {
|
|
| 142 |
- defaults[i] = "" // Used to indicate it should be removed |
|
| 219 |
+// WithRootFSPath specifies unmanaged rootfs path. |
|
| 220 |
+func WithRootFSPath(path string) SpecOpts {
|
|
| 221 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 222 |
+ setRoot(s) |
|
| 223 |
+ s.Root.Path = path |
|
| 224 |
+ // Entrypoint is not set here (it's up to caller) |
|
| 225 |
+ return nil |
|
| 226 |
+ } |
|
| 227 |
+} |
|
| 228 |
+ |
|
| 229 |
+// WithRootFSReadonly sets specs.Root.Readonly to true |
|
| 230 |
+func WithRootFSReadonly() SpecOpts {
|
|
| 231 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 232 |
+ setRoot(s) |
|
| 233 |
+ s.Root.Readonly = true |
|
| 234 |
+ return nil |
|
| 235 |
+ } |
|
| 236 |
+} |
|
| 237 |
+ |
|
| 238 |
+// WithNoNewPrivileges sets no_new_privileges on the process for the container |
|
| 239 |
+func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 240 |
+ setProcess(s) |
|
| 241 |
+ s.Process.NoNewPrivileges = true |
|
| 242 |
+ return nil |
|
| 243 |
+} |
|
| 244 |
+ |
|
| 245 |
+// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly |
|
| 246 |
+func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 247 |
+ s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 248 |
+ Destination: "/etc/hosts", |
|
| 249 |
+ Type: "bind", |
|
| 250 |
+ Source: "/etc/hosts", |
|
| 251 |
+ Options: []string{"rbind", "ro"},
|
|
| 252 |
+ }) |
|
| 253 |
+ return nil |
|
| 254 |
+} |
|
| 255 |
+ |
|
| 256 |
+// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly |
|
| 257 |
+func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 258 |
+ s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 259 |
+ Destination: "/etc/resolv.conf", |
|
| 260 |
+ Type: "bind", |
|
| 261 |
+ Source: "/etc/resolv.conf", |
|
| 262 |
+ Options: []string{"rbind", "ro"},
|
|
| 263 |
+ }) |
|
| 264 |
+ return nil |
|
| 265 |
+} |
|
| 266 |
+ |
|
| 267 |
+// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly |
|
| 268 |
+func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 269 |
+ s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 270 |
+ Destination: "/etc/localtime", |
|
| 271 |
+ Type: "bind", |
|
| 272 |
+ Source: "/etc/localtime", |
|
| 273 |
+ Options: []string{"rbind", "ro"},
|
|
| 274 |
+ }) |
|
| 275 |
+ return nil |
|
| 276 |
+} |
|
| 277 |
+ |
|
| 278 |
+// WithUserNamespace sets the uid and gid mappings for the task |
|
| 279 |
+// this can be called multiple times to add more mappings to the generated spec |
|
| 280 |
+func WithUserNamespace(container, host, size uint32) SpecOpts {
|
|
| 281 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 282 |
+ var hasUserns bool |
|
| 283 |
+ setLinux(s) |
|
| 284 |
+ for _, ns := range s.Linux.Namespaces {
|
|
| 285 |
+ if ns.Type == specs.UserNamespace {
|
|
| 286 |
+ hasUserns = true |
|
| 287 |
+ break |
|
| 143 | 288 |
} |
| 144 |
- continue |
|
| 145 | 289 |
} |
| 290 |
+ if !hasUserns {
|
|
| 291 |
+ s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
|
|
| 292 |
+ Type: specs.UserNamespace, |
|
| 293 |
+ }) |
|
| 294 |
+ } |
|
| 295 |
+ mapping := specs.LinuxIDMapping{
|
|
| 296 |
+ ContainerID: container, |
|
| 297 |
+ HostID: host, |
|
| 298 |
+ Size: size, |
|
| 299 |
+ } |
|
| 300 |
+ s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) |
|
| 301 |
+ s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) |
|
| 302 |
+ return nil |
|
| 303 |
+ } |
|
| 304 |
+} |
|
| 146 | 305 |
|
| 147 |
- // Just do a normal set/update |
|
| 148 |
- parts := strings.SplitN(value, "=", 2) |
|
| 149 |
- if i, exists := cache[parts[0]]; exists {
|
|
| 150 |
- defaults[i] = value |
|
| 306 |
+// WithCgroup sets the container's cgroup path |
|
| 307 |
+func WithCgroup(path string) SpecOpts {
|
|
| 308 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 309 |
+ setLinux(s) |
|
| 310 |
+ s.Linux.CgroupsPath = path |
|
| 311 |
+ return nil |
|
| 312 |
+ } |
|
| 313 |
+} |
|
| 314 |
+ |
|
| 315 |
+// WithNamespacedCgroup uses the namespace set on the context to create a |
|
| 316 |
+// root directory for containers in the cgroup with the id as the subcgroup |
|
| 317 |
+func WithNamespacedCgroup() SpecOpts {
|
|
| 318 |
+ return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
|
| 319 |
+ namespace, err := namespaces.NamespaceRequired(ctx) |
|
| 320 |
+ if err != nil {
|
|
| 321 |
+ return err |
|
| 322 |
+ } |
|
| 323 |
+ setLinux(s) |
|
| 324 |
+ s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
|
| 325 |
+ return nil |
|
| 326 |
+ } |
|
| 327 |
+} |
|
| 328 |
+ |
|
| 329 |
+// WithUser sets the user to be used within the container. |
|
| 330 |
+// It accepts a valid user string in OCI Image Spec v1.0.0: |
|
| 331 |
+// user, uid, user:group, uid:gid, uid:group, user:gid |
|
| 332 |
+func WithUser(userstr string) SpecOpts {
|
|
| 333 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
| 334 |
+ setProcess(s) |
|
| 335 |
+ parts := strings.Split(userstr, ":") |
|
| 336 |
+ switch len(parts) {
|
|
| 337 |
+ case 1: |
|
| 338 |
+ v, err := strconv.Atoi(parts[0]) |
|
| 339 |
+ if err != nil {
|
|
| 340 |
+ // if we cannot parse as a uint they try to see if it is a username |
|
| 341 |
+ return WithUsername(userstr)(ctx, client, c, s) |
|
| 342 |
+ } |
|
| 343 |
+ return WithUserID(uint32(v))(ctx, client, c, s) |
|
| 344 |
+ case 2: |
|
| 345 |
+ var ( |
|
| 346 |
+ username string |
|
| 347 |
+ groupname string |
|
| 348 |
+ ) |
|
| 349 |
+ var uid, gid uint32 |
|
| 350 |
+ v, err := strconv.Atoi(parts[0]) |
|
| 351 |
+ if err != nil {
|
|
| 352 |
+ username = parts[0] |
|
| 353 |
+ } else {
|
|
| 354 |
+ uid = uint32(v) |
|
| 355 |
+ } |
|
| 356 |
+ if v, err = strconv.Atoi(parts[1]); err != nil {
|
|
| 357 |
+ groupname = parts[1] |
|
| 358 |
+ } else {
|
|
| 359 |
+ gid = uint32(v) |
|
| 360 |
+ } |
|
| 361 |
+ if username == "" && groupname == "" {
|
|
| 362 |
+ s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 363 |
+ return nil |
|
| 364 |
+ } |
|
| 365 |
+ f := func(root string) error {
|
|
| 366 |
+ if username != "" {
|
|
| 367 |
+ user, err := getUserFromPath(root, func(u user.User) bool {
|
|
| 368 |
+ return u.Name == username |
|
| 369 |
+ }) |
|
| 370 |
+ if err != nil {
|
|
| 371 |
+ return err |
|
| 372 |
+ } |
|
| 373 |
+ uid = uint32(user.Uid) |
|
| 374 |
+ } |
|
| 375 |
+ if groupname != "" {
|
|
| 376 |
+ gid, err = getGIDFromPath(root, func(g user.Group) bool {
|
|
| 377 |
+ return g.Name == groupname |
|
| 378 |
+ }) |
|
| 379 |
+ if err != nil {
|
|
| 380 |
+ return err |
|
| 381 |
+ } |
|
| 382 |
+ } |
|
| 383 |
+ s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 384 |
+ return nil |
|
| 385 |
+ } |
|
| 386 |
+ if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 387 |
+ if !isRootfsAbs(s.Root.Path) {
|
|
| 388 |
+ return errors.New("rootfs absolute path is required")
|
|
| 389 |
+ } |
|
| 390 |
+ return f(s.Root.Path) |
|
| 391 |
+ } |
|
| 392 |
+ if c.Snapshotter == "" {
|
|
| 393 |
+ return errors.New("no snapshotter set for container")
|
|
| 394 |
+ } |
|
| 395 |
+ if c.SnapshotKey == "" {
|
|
| 396 |
+ return errors.New("rootfs snapshot not created for container")
|
|
| 397 |
+ } |
|
| 398 |
+ snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 399 |
+ mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 400 |
+ if err != nil {
|
|
| 401 |
+ return err |
|
| 402 |
+ } |
|
| 403 |
+ return mount.WithTempMount(ctx, mounts, f) |
|
| 404 |
+ default: |
|
| 405 |
+ return fmt.Errorf("invalid USER value %s", userstr)
|
|
| 406 |
+ } |
|
| 407 |
+ } |
|
| 408 |
+} |
|
| 409 |
+ |
|
| 410 |
+// WithUIDGID allows the UID and GID for the Process to be set |
|
| 411 |
+func WithUIDGID(uid, gid uint32) SpecOpts {
|
|
| 412 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 413 |
+ setProcess(s) |
|
| 414 |
+ s.Process.User.UID = uid |
|
| 415 |
+ s.Process.User.GID = gid |
|
| 416 |
+ return nil |
|
| 417 |
+ } |
|
| 418 |
+} |
|
| 419 |
+ |
|
| 420 |
+// WithUserID sets the correct UID and GID for the container based |
|
| 421 |
+// on the image's /etc/passwd contents. If /etc/passwd does not exist, |
|
| 422 |
+// or uid is not found in /etc/passwd, it sets the requested uid, |
|
| 423 |
+// additionally sets the gid to 0, and does not return an error. |
|
| 424 |
+func WithUserID(uid uint32) SpecOpts {
|
|
| 425 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
|
| 426 |
+ setProcess(s) |
|
| 427 |
+ if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 428 |
+ if !isRootfsAbs(s.Root.Path) {
|
|
| 429 |
+ return errors.Errorf("rootfs absolute path is required")
|
|
| 430 |
+ } |
|
| 431 |
+ user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
|
|
| 432 |
+ return u.Uid == int(uid) |
|
| 433 |
+ }) |
|
| 434 |
+ if err != nil {
|
|
| 435 |
+ if os.IsNotExist(err) || err == errNoUsersFound {
|
|
| 436 |
+ s.Process.User.UID, s.Process.User.GID = uid, 0 |
|
| 437 |
+ return nil |
|
| 438 |
+ } |
|
| 439 |
+ return err |
|
| 440 |
+ } |
|
| 441 |
+ s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid) |
|
| 442 |
+ return nil |
|
| 443 |
+ |
|
| 444 |
+ } |
|
| 445 |
+ if c.Snapshotter == "" {
|
|
| 446 |
+ return errors.Errorf("no snapshotter set for container")
|
|
| 447 |
+ } |
|
| 448 |
+ if c.SnapshotKey == "" {
|
|
| 449 |
+ return errors.Errorf("rootfs snapshot not created for container")
|
|
| 450 |
+ } |
|
| 451 |
+ snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 452 |
+ mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 453 |
+ if err != nil {
|
|
| 454 |
+ return err |
|
| 455 |
+ } |
|
| 456 |
+ return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
| 457 |
+ user, err := getUserFromPath(root, func(u user.User) bool {
|
|
| 458 |
+ return u.Uid == int(uid) |
|
| 459 |
+ }) |
|
| 460 |
+ if err != nil {
|
|
| 461 |
+ if os.IsNotExist(err) || err == errNoUsersFound {
|
|
| 462 |
+ s.Process.User.UID, s.Process.User.GID = uid, 0 |
|
| 463 |
+ return nil |
|
| 464 |
+ } |
|
| 465 |
+ return err |
|
| 466 |
+ } |
|
| 467 |
+ s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid) |
|
| 468 |
+ return nil |
|
| 469 |
+ }) |
|
| 470 |
+ } |
|
| 471 |
+} |
|
| 472 |
+ |
|
| 473 |
+// WithUsername sets the correct UID and GID for the container |
|
| 474 |
+// based on the the image's /etc/passwd contents. If /etc/passwd |
|
| 475 |
+// does not exist, or the username is not found in /etc/passwd, |
|
| 476 |
+// it returns error. |
|
| 477 |
+func WithUsername(username string) SpecOpts {
|
|
| 478 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
|
| 479 |
+ setProcess(s) |
|
| 480 |
+ if s.Linux != nil {
|
|
| 481 |
+ if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 482 |
+ if !isRootfsAbs(s.Root.Path) {
|
|
| 483 |
+ return errors.Errorf("rootfs absolute path is required")
|
|
| 484 |
+ } |
|
| 485 |
+ user, err := getUserFromPath(s.Root.Path, func(u user.User) bool {
|
|
| 486 |
+ return u.Name == username |
|
| 487 |
+ }) |
|
| 488 |
+ if err != nil {
|
|
| 489 |
+ return err |
|
| 490 |
+ } |
|
| 491 |
+ s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid) |
|
| 492 |
+ return nil |
|
| 493 |
+ } |
|
| 494 |
+ if c.Snapshotter == "" {
|
|
| 495 |
+ return errors.Errorf("no snapshotter set for container")
|
|
| 496 |
+ } |
|
| 497 |
+ if c.SnapshotKey == "" {
|
|
| 498 |
+ return errors.Errorf("rootfs snapshot not created for container")
|
|
| 499 |
+ } |
|
| 500 |
+ snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 501 |
+ mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 502 |
+ if err != nil {
|
|
| 503 |
+ return err |
|
| 504 |
+ } |
|
| 505 |
+ return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
| 506 |
+ user, err := getUserFromPath(root, func(u user.User) bool {
|
|
| 507 |
+ return u.Name == username |
|
| 508 |
+ }) |
|
| 509 |
+ if err != nil {
|
|
| 510 |
+ return err |
|
| 511 |
+ } |
|
| 512 |
+ s.Process.User.UID, s.Process.User.GID = uint32(user.Uid), uint32(user.Gid) |
|
| 513 |
+ return nil |
|
| 514 |
+ }) |
|
| 515 |
+ } else if s.Windows != nil {
|
|
| 516 |
+ s.Process.User.Username = username |
|
| 151 | 517 |
} else {
|
| 152 |
- defaults = append(defaults, value) |
|
| 518 |
+ return errors.New("spec does not contain Linux or Windows section")
|
|
| 153 | 519 |
} |
| 520 |
+ return nil |
|
| 154 | 521 |
} |
| 522 |
+} |
|
| 155 | 523 |
|
| 156 |
- // Now remove all entries that we want to "unset" |
|
| 157 |
- for i := 0; i < len(defaults); i++ {
|
|
| 158 |
- if defaults[i] == "" {
|
|
| 159 |
- defaults = append(defaults[:i], defaults[i+1:]...) |
|
| 160 |
- i-- |
|
| 524 |
+// WithAdditionalGIDs sets the OCI spec's additionalGids array to any additional groups listed |
|
| 525 |
+// for a particular user in the /etc/groups file of the image's root filesystem |
|
| 526 |
+// The passed in user can be either a uid or a username. |
|
| 527 |
+func WithAdditionalGIDs(userstr string) SpecOpts {
|
|
| 528 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
|
| 529 |
+ setProcess(s) |
|
| 530 |
+ setAdditionalGids := func(root string) error {
|
|
| 531 |
+ var username string |
|
| 532 |
+ uid, err := strconv.Atoi(userstr) |
|
| 533 |
+ if err == nil {
|
|
| 534 |
+ user, err := getUserFromPath(root, func(u user.User) bool {
|
|
| 535 |
+ return u.Uid == uid |
|
| 536 |
+ }) |
|
| 537 |
+ if err != nil {
|
|
| 538 |
+ if os.IsNotExist(err) || err == errNoUsersFound {
|
|
| 539 |
+ return nil |
|
| 540 |
+ } |
|
| 541 |
+ return err |
|
| 542 |
+ } |
|
| 543 |
+ username = user.Name |
|
| 544 |
+ } else {
|
|
| 545 |
+ username = userstr |
|
| 546 |
+ } |
|
| 547 |
+ gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool {
|
|
| 548 |
+ // we only want supplemental groups |
|
| 549 |
+ if g.Name == username {
|
|
| 550 |
+ return false |
|
| 551 |
+ } |
|
| 552 |
+ for _, entry := range g.List {
|
|
| 553 |
+ if entry == username {
|
|
| 554 |
+ return true |
|
| 555 |
+ } |
|
| 556 |
+ } |
|
| 557 |
+ return false |
|
| 558 |
+ }) |
|
| 559 |
+ if err != nil {
|
|
| 560 |
+ if os.IsNotExist(err) {
|
|
| 561 |
+ return nil |
|
| 562 |
+ } |
|
| 563 |
+ return err |
|
| 564 |
+ } |
|
| 565 |
+ s.Process.User.AdditionalGids = gids |
|
| 566 |
+ return nil |
|
| 567 |
+ } |
|
| 568 |
+ if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 569 |
+ if !isRootfsAbs(s.Root.Path) {
|
|
| 570 |
+ return errors.Errorf("rootfs absolute path is required")
|
|
| 571 |
+ } |
|
| 572 |
+ return setAdditionalGids(s.Root.Path) |
|
| 161 | 573 |
} |
| 574 |
+ if c.Snapshotter == "" {
|
|
| 575 |
+ return errors.Errorf("no snapshotter set for container")
|
|
| 576 |
+ } |
|
| 577 |
+ if c.SnapshotKey == "" {
|
|
| 578 |
+ return errors.Errorf("rootfs snapshot not created for container")
|
|
| 579 |
+ } |
|
| 580 |
+ snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 581 |
+ mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 582 |
+ if err != nil {
|
|
| 583 |
+ return err |
|
| 584 |
+ } |
|
| 585 |
+ return mount.WithTempMount(ctx, mounts, setAdditionalGids) |
|
| 162 | 586 |
} |
| 587 |
+} |
|
| 163 | 588 |
|
| 164 |
- return defaults |
|
| 589 |
+// WithCapabilities sets Linux capabilities on the process |
|
| 590 |
+func WithCapabilities(caps []string) SpecOpts {
|
|
| 591 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 592 |
+ setCapabilities(s) |
|
| 593 |
+ |
|
| 594 |
+ s.Process.Capabilities.Bounding = caps |
|
| 595 |
+ s.Process.Capabilities.Effective = caps |
|
| 596 |
+ s.Process.Capabilities.Permitted = caps |
|
| 597 |
+ s.Process.Capabilities.Inheritable = caps |
|
| 598 |
+ |
|
| 599 |
+ return nil |
|
| 600 |
+ } |
|
| 601 |
+} |
|
| 602 |
+ |
|
| 603 |
+// WithAllCapabilities sets all linux capabilities for the process |
|
| 604 |
+var WithAllCapabilities = WithCapabilities(getAllCapabilities()) |
|
| 605 |
+ |
|
| 606 |
+func getAllCapabilities() []string {
|
|
| 607 |
+ last := capability.CAP_LAST_CAP |
|
| 608 |
+ // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap |
|
| 609 |
+ if last == capability.Cap(63) {
|
|
| 610 |
+ last = capability.CAP_BLOCK_SUSPEND |
|
| 611 |
+ } |
|
| 612 |
+ var caps []string |
|
| 613 |
+ for _, cap := range capability.List() {
|
|
| 614 |
+ if cap > last {
|
|
| 615 |
+ continue |
|
| 616 |
+ } |
|
| 617 |
+ caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) |
|
| 618 |
+ } |
|
| 619 |
+ return caps |
|
| 620 |
+} |
|
| 621 |
+ |
|
| 622 |
+// WithAmbientCapabilities set the Linux ambient capabilities for the process |
|
| 623 |
+// Ambient capabilities should only be set for non-root users or the caller should |
|
| 624 |
+// understand how these capabilities are used and set |
|
| 625 |
+func WithAmbientCapabilities(caps []string) SpecOpts {
|
|
| 626 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 627 |
+ setCapabilities(s) |
|
| 628 |
+ |
|
| 629 |
+ s.Process.Capabilities.Ambient = caps |
|
| 630 |
+ return nil |
|
| 631 |
+ } |
|
| 632 |
+} |
|
| 633 |
+ |
|
| 634 |
+var errNoUsersFound = errors.New("no users found")
|
|
| 635 |
+ |
|
| 636 |
+func getUserFromPath(root string, filter func(user.User) bool) (user.User, error) {
|
|
| 637 |
+ ppath, err := fs.RootPath(root, "/etc/passwd") |
|
| 638 |
+ if err != nil {
|
|
| 639 |
+ return user.User{}, err
|
|
| 640 |
+ } |
|
| 641 |
+ users, err := user.ParsePasswdFileFilter(ppath, filter) |
|
| 642 |
+ if err != nil {
|
|
| 643 |
+ return user.User{}, err
|
|
| 644 |
+ } |
|
| 645 |
+ if len(users) == 0 {
|
|
| 646 |
+ return user.User{}, errNoUsersFound
|
|
| 647 |
+ } |
|
| 648 |
+ return users[0], nil |
|
| 649 |
+} |
|
| 650 |
+ |
|
| 651 |
+var errNoGroupsFound = errors.New("no groups found")
|
|
| 652 |
+ |
|
| 653 |
+func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
|
|
| 654 |
+ gpath, err := fs.RootPath(root, "/etc/group") |
|
| 655 |
+ if err != nil {
|
|
| 656 |
+ return 0, err |
|
| 657 |
+ } |
|
| 658 |
+ groups, err := user.ParseGroupFileFilter(gpath, filter) |
|
| 659 |
+ if err != nil {
|
|
| 660 |
+ return 0, err |
|
| 661 |
+ } |
|
| 662 |
+ if len(groups) == 0 {
|
|
| 663 |
+ return 0, errNoGroupsFound |
|
| 664 |
+ } |
|
| 665 |
+ g := groups[0] |
|
| 666 |
+ return uint32(g.Gid), nil |
|
| 667 |
+} |
|
| 668 |
+ |
|
| 669 |
+func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([]uint32, error) {
|
|
| 670 |
+ gpath, err := fs.RootPath(root, "/etc/group") |
|
| 671 |
+ if err != nil {
|
|
| 672 |
+ return []uint32{}, err
|
|
| 673 |
+ } |
|
| 674 |
+ groups, err := user.ParseGroupFileFilter(gpath, filter) |
|
| 675 |
+ if err != nil {
|
|
| 676 |
+ return []uint32{}, err
|
|
| 677 |
+ } |
|
| 678 |
+ if len(groups) == 0 {
|
|
| 679 |
+ // if there are no additional groups; just return an empty set |
|
| 680 |
+ return []uint32{}, nil
|
|
| 681 |
+ } |
|
| 682 |
+ addlGids := []uint32{}
|
|
| 683 |
+ for _, grp := range groups {
|
|
| 684 |
+ addlGids = append(addlGids, uint32(grp.Gid)) |
|
| 685 |
+ } |
|
| 686 |
+ return addlGids, nil |
|
| 687 |
+} |
|
| 688 |
+ |
|
| 689 |
+func isRootfsAbs(root string) bool {
|
|
| 690 |
+ return filepath.IsAbs(root) |
|
| 691 |
+} |
|
| 692 |
+ |
|
| 693 |
+// WithMaskedPaths sets the masked paths option |
|
| 694 |
+func WithMaskedPaths(paths []string) SpecOpts {
|
|
| 695 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 696 |
+ setLinux(s) |
|
| 697 |
+ s.Linux.MaskedPaths = paths |
|
| 698 |
+ return nil |
|
| 699 |
+ } |
|
| 700 |
+} |
|
| 701 |
+ |
|
| 702 |
+// WithReadonlyPaths sets the read only paths option |
|
| 703 |
+func WithReadonlyPaths(paths []string) SpecOpts {
|
|
| 704 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 705 |
+ setLinux(s) |
|
| 706 |
+ s.Linux.ReadonlyPaths = paths |
|
| 707 |
+ return nil |
|
| 708 |
+ } |
|
| 709 |
+} |
|
| 710 |
+ |
|
| 711 |
+// WithWriteableSysfs makes any sysfs mounts writeable |
|
| 712 |
+func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 713 |
+ for i, m := range s.Mounts {
|
|
| 714 |
+ if m.Type == "sysfs" {
|
|
| 715 |
+ var options []string |
|
| 716 |
+ for _, o := range m.Options {
|
|
| 717 |
+ if o == "ro" {
|
|
| 718 |
+ o = "rw" |
|
| 719 |
+ } |
|
| 720 |
+ options = append(options, o) |
|
| 721 |
+ } |
|
| 722 |
+ s.Mounts[i].Options = options |
|
| 723 |
+ } |
|
| 724 |
+ } |
|
| 725 |
+ return nil |
|
| 726 |
+} |
|
| 727 |
+ |
|
| 728 |
+// WithWriteableCgroupfs makes any cgroup mounts writeable |
|
| 729 |
+func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 730 |
+ for i, m := range s.Mounts {
|
|
| 731 |
+ if m.Type == "cgroup" {
|
|
| 732 |
+ var options []string |
|
| 733 |
+ for _, o := range m.Options {
|
|
| 734 |
+ if o == "ro" {
|
|
| 735 |
+ o = "rw" |
|
| 736 |
+ } |
|
| 737 |
+ options = append(options, o) |
|
| 738 |
+ } |
|
| 739 |
+ s.Mounts[i].Options = options |
|
| 740 |
+ } |
|
| 741 |
+ } |
|
| 742 |
+ return nil |
|
| 165 | 743 |
} |
| 744 |
+ |
|
| 745 |
+// WithSelinuxLabel sets the process SELinux label |
|
| 746 |
+func WithSelinuxLabel(label string) SpecOpts {
|
|
| 747 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 748 |
+ setProcess(s) |
|
| 749 |
+ s.Process.SelinuxLabel = label |
|
| 750 |
+ return nil |
|
| 751 |
+ } |
|
| 752 |
+} |
|
| 753 |
+ |
|
| 754 |
+// WithApparmorProfile sets the Apparmor profile for the process |
|
| 755 |
+func WithApparmorProfile(profile string) SpecOpts {
|
|
| 756 |
+ return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 757 |
+ setProcess(s) |
|
| 758 |
+ s.Process.ApparmorProfile = profile |
|
| 759 |
+ return nil |
|
| 760 |
+ } |
|
| 761 |
+} |
|
| 762 |
+ |
|
| 763 |
+// WithSeccompUnconfined clears the seccomp profile |
|
| 764 |
+func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 765 |
+ setLinux(s) |
|
| 766 |
+ s.Linux.Seccomp = nil |
|
| 767 |
+ return nil |
|
| 768 |
+} |
|
| 769 |
+ |
|
| 770 |
+// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's |
|
| 771 |
+// allowed and denied devices |
|
| 772 |
+func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 773 |
+ setLinux(s) |
|
| 774 |
+ if s.Linux.Resources == nil {
|
|
| 775 |
+ s.Linux.Resources = &specs.LinuxResources{}
|
|
| 776 |
+ } |
|
| 777 |
+ s.Linux.Resources.Devices = nil |
|
| 778 |
+ return nil |
|
| 779 |
+} |
|
| 780 |
+ |
|
| 781 |
+// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to |
|
| 782 |
+// the container's resource cgroup spec |
|
| 783 |
+func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 784 |
+ setLinux(s) |
|
| 785 |
+ if s.Linux.Resources == nil {
|
|
| 786 |
+ s.Linux.Resources = &specs.LinuxResources{}
|
|
| 787 |
+ } |
|
| 788 |
+ intptr := func(i int64) *int64 {
|
|
| 789 |
+ return &i |
|
| 790 |
+ } |
|
| 791 |
+ s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
|
| 792 |
+ {
|
|
| 793 |
+ // "/dev/null", |
|
| 794 |
+ Type: "c", |
|
| 795 |
+ Major: intptr(1), |
|
| 796 |
+ Minor: intptr(3), |
|
| 797 |
+ Access: rwm, |
|
| 798 |
+ Allow: true, |
|
| 799 |
+ }, |
|
| 800 |
+ {
|
|
| 801 |
+ // "/dev/random", |
|
| 802 |
+ Type: "c", |
|
| 803 |
+ Major: intptr(1), |
|
| 804 |
+ Minor: intptr(8), |
|
| 805 |
+ Access: rwm, |
|
| 806 |
+ Allow: true, |
|
| 807 |
+ }, |
|
| 808 |
+ {
|
|
| 809 |
+ // "/dev/full", |
|
| 810 |
+ Type: "c", |
|
| 811 |
+ Major: intptr(1), |
|
| 812 |
+ Minor: intptr(7), |
|
| 813 |
+ Access: rwm, |
|
| 814 |
+ Allow: true, |
|
| 815 |
+ }, |
|
| 816 |
+ {
|
|
| 817 |
+ // "/dev/tty", |
|
| 818 |
+ Type: "c", |
|
| 819 |
+ Major: intptr(5), |
|
| 820 |
+ Minor: intptr(0), |
|
| 821 |
+ Access: rwm, |
|
| 822 |
+ Allow: true, |
|
| 823 |
+ }, |
|
| 824 |
+ {
|
|
| 825 |
+ // "/dev/zero", |
|
| 826 |
+ Type: "c", |
|
| 827 |
+ Major: intptr(1), |
|
| 828 |
+ Minor: intptr(5), |
|
| 829 |
+ Access: rwm, |
|
| 830 |
+ Allow: true, |
|
| 831 |
+ }, |
|
| 832 |
+ {
|
|
| 833 |
+ // "/dev/urandom", |
|
| 834 |
+ Type: "c", |
|
| 835 |
+ Major: intptr(1), |
|
| 836 |
+ Minor: intptr(9), |
|
| 837 |
+ Access: rwm, |
|
| 838 |
+ Allow: true, |
|
| 839 |
+ }, |
|
| 840 |
+ {
|
|
| 841 |
+ // "/dev/console", |
|
| 842 |
+ Type: "c", |
|
| 843 |
+ Major: intptr(5), |
|
| 844 |
+ Minor: intptr(1), |
|
| 845 |
+ Access: rwm, |
|
| 846 |
+ Allow: true, |
|
| 847 |
+ }, |
|
| 848 |
+ // /dev/pts/ - pts namespaces are "coming soon" |
|
| 849 |
+ {
|
|
| 850 |
+ Type: "c", |
|
| 851 |
+ Major: intptr(136), |
|
| 852 |
+ Access: rwm, |
|
| 853 |
+ Allow: true, |
|
| 854 |
+ }, |
|
| 855 |
+ {
|
|
| 856 |
+ Type: "c", |
|
| 857 |
+ Major: intptr(5), |
|
| 858 |
+ Minor: intptr(2), |
|
| 859 |
+ Access: rwm, |
|
| 860 |
+ Allow: true, |
|
| 861 |
+ }, |
|
| 862 |
+ {
|
|
| 863 |
+ // tuntap |
|
| 864 |
+ Type: "c", |
|
| 865 |
+ Major: intptr(10), |
|
| 866 |
+ Minor: intptr(200), |
|
| 867 |
+ Access: rwm, |
|
| 868 |
+ Allow: true, |
|
| 869 |
+ }, |
|
| 870 |
+ }...) |
|
| 871 |
+ return nil |
|
| 872 |
+} |
|
| 873 |
+ |
|
| 874 |
+// WithPrivileged sets up options for a privileged container |
|
| 875 |
+// TODO(justincormack) device handling |
|
| 876 |
+var WithPrivileged = Compose( |
|
| 877 |
+ WithAllCapabilities, |
|
| 878 |
+ WithMaskedPaths(nil), |
|
| 879 |
+ WithReadonlyPaths(nil), |
|
| 880 |
+ WithWriteableSysfs, |
|
| 881 |
+ WithWriteableCgroupfs, |
|
| 882 |
+ WithSelinuxLabel(""),
|
|
| 883 |
+ WithApparmorProfile(""),
|
|
| 884 |
+ WithSeccompUnconfined, |
|
| 885 |
+) |
| 166 | 886 |
deleted file mode 100644 |
| ... | ... |
@@ -1,733 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-/* |
|
| 4 |
- Copyright The containerd Authors. |
|
| 5 |
- |
|
| 6 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 7 |
- you may not use this file except in compliance with the License. |
|
| 8 |
- You may obtain a copy of the License at |
|
| 9 |
- |
|
| 10 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 11 |
- |
|
| 12 |
- Unless required by applicable law or agreed to in writing, software |
|
| 13 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 14 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 15 |
- See the License for the specific language governing permissions and |
|
| 16 |
- limitations under the License. |
|
| 17 |
-*/ |
|
| 18 |
- |
|
| 19 |
-package oci |
|
| 20 |
- |
|
| 21 |
-import ( |
|
| 22 |
- "context" |
|
| 23 |
- "encoding/json" |
|
| 24 |
- "fmt" |
|
| 25 |
- "os" |
|
| 26 |
- "path/filepath" |
|
| 27 |
- "strconv" |
|
| 28 |
- "strings" |
|
| 29 |
- |
|
| 30 |
- "github.com/containerd/containerd/containers" |
|
| 31 |
- "github.com/containerd/containerd/content" |
|
| 32 |
- "github.com/containerd/containerd/images" |
|
| 33 |
- "github.com/containerd/containerd/mount" |
|
| 34 |
- "github.com/containerd/containerd/namespaces" |
|
| 35 |
- "github.com/containerd/continuity/fs" |
|
| 36 |
- "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 37 |
- "github.com/opencontainers/runc/libcontainer/user" |
|
| 38 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 39 |
- "github.com/pkg/errors" |
|
| 40 |
- "github.com/syndtr/gocapability/capability" |
|
| 41 |
-) |
|
| 42 |
- |
|
| 43 |
-// WithTTY sets the information on the spec as well as the environment variables for |
|
| 44 |
-// using a TTY |
|
| 45 |
-func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 46 |
- setProcess(s) |
|
| 47 |
- s.Process.Terminal = true |
|
| 48 |
- s.Process.Env = append(s.Process.Env, "TERM=xterm") |
|
| 49 |
- return nil |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-// setRoot sets Root to empty if unset |
|
| 53 |
-func setRoot(s *Spec) {
|
|
| 54 |
- if s.Root == nil {
|
|
| 55 |
- s.Root = &specs.Root{}
|
|
| 56 |
- } |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-// setLinux sets Linux to empty if unset |
|
| 60 |
-func setLinux(s *Spec) {
|
|
| 61 |
- if s.Linux == nil {
|
|
| 62 |
- s.Linux = &specs.Linux{}
|
|
| 63 |
- } |
|
| 64 |
-} |
|
| 65 |
- |
|
| 66 |
-// setCapabilities sets Linux Capabilities to empty if unset |
|
| 67 |
-func setCapabilities(s *Spec) {
|
|
| 68 |
- setProcess(s) |
|
| 69 |
- if s.Process.Capabilities == nil {
|
|
| 70 |
- s.Process.Capabilities = &specs.LinuxCapabilities{}
|
|
| 71 |
- } |
|
| 72 |
-} |
|
| 73 |
- |
|
| 74 |
-// WithHostNamespace allows a task to run inside the host's linux namespace |
|
| 75 |
-func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
|
| 76 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 77 |
- setLinux(s) |
|
| 78 |
- for i, n := range s.Linux.Namespaces {
|
|
| 79 |
- if n.Type == ns {
|
|
| 80 |
- s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) |
|
| 81 |
- return nil |
|
| 82 |
- } |
|
| 83 |
- } |
|
| 84 |
- return nil |
|
| 85 |
- } |
|
| 86 |
-} |
|
| 87 |
- |
|
| 88 |
-// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the |
|
| 89 |
-// spec, the existing namespace is replaced by the one provided. |
|
| 90 |
-func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
|
| 91 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 92 |
- setLinux(s) |
|
| 93 |
- for i, n := range s.Linux.Namespaces {
|
|
| 94 |
- if n.Type == ns.Type {
|
|
| 95 |
- before := s.Linux.Namespaces[:i] |
|
| 96 |
- after := s.Linux.Namespaces[i+1:] |
|
| 97 |
- s.Linux.Namespaces = append(before, ns) |
|
| 98 |
- s.Linux.Namespaces = append(s.Linux.Namespaces, after...) |
|
| 99 |
- return nil |
|
| 100 |
- } |
|
| 101 |
- } |
|
| 102 |
- s.Linux.Namespaces = append(s.Linux.Namespaces, ns) |
|
| 103 |
- return nil |
|
| 104 |
- } |
|
| 105 |
-} |
|
| 106 |
- |
|
| 107 |
-// WithImageConfig configures the spec to from the configuration of an Image |
|
| 108 |
-func WithImageConfig(image Image) SpecOpts {
|
|
| 109 |
- return WithImageConfigArgs(image, nil) |
|
| 110 |
-} |
|
| 111 |
- |
|
| 112 |
-// WithImageConfigArgs configures the spec to from the configuration of an Image with additional args that |
|
| 113 |
-// replaces the CMD of the image |
|
| 114 |
-func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
|
| 115 |
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
| 116 |
- ic, err := image.Config(ctx) |
|
| 117 |
- if err != nil {
|
|
| 118 |
- return err |
|
| 119 |
- } |
|
| 120 |
- var ( |
|
| 121 |
- ociimage v1.Image |
|
| 122 |
- config v1.ImageConfig |
|
| 123 |
- ) |
|
| 124 |
- switch ic.MediaType {
|
|
| 125 |
- case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: |
|
| 126 |
- p, err := content.ReadBlob(ctx, image.ContentStore(), ic) |
|
| 127 |
- if err != nil {
|
|
| 128 |
- return err |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 |
- if err := json.Unmarshal(p, &ociimage); err != nil {
|
|
| 132 |
- return err |
|
| 133 |
- } |
|
| 134 |
- config = ociimage.Config |
|
| 135 |
- default: |
|
| 136 |
- return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
|
| 137 |
- } |
|
| 138 |
- |
|
| 139 |
- setProcess(s) |
|
| 140 |
- s.Process.Env = append(s.Process.Env, config.Env...) |
|
| 141 |
- cmd := config.Cmd |
|
| 142 |
- if len(args) > 0 {
|
|
| 143 |
- cmd = args |
|
| 144 |
- } |
|
| 145 |
- s.Process.Args = append(config.Entrypoint, cmd...) |
|
| 146 |
- |
|
| 147 |
- cwd := config.WorkingDir |
|
| 148 |
- if cwd == "" {
|
|
| 149 |
- cwd = "/" |
|
| 150 |
- } |
|
| 151 |
- s.Process.Cwd = cwd |
|
| 152 |
- if config.User != "" {
|
|
| 153 |
- return WithUser(config.User)(ctx, client, c, s) |
|
| 154 |
- } |
|
| 155 |
- return nil |
|
| 156 |
- } |
|
| 157 |
-} |
|
| 158 |
- |
|
| 159 |
-// WithRootFSPath specifies unmanaged rootfs path. |
|
| 160 |
-func WithRootFSPath(path string) SpecOpts {
|
|
| 161 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 162 |
- setRoot(s) |
|
| 163 |
- s.Root.Path = path |
|
| 164 |
- // Entrypoint is not set here (it's up to caller) |
|
| 165 |
- return nil |
|
| 166 |
- } |
|
| 167 |
-} |
|
| 168 |
- |
|
| 169 |
-// WithRootFSReadonly sets specs.Root.Readonly to true |
|
| 170 |
-func WithRootFSReadonly() SpecOpts {
|
|
| 171 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 172 |
- setRoot(s) |
|
| 173 |
- s.Root.Readonly = true |
|
| 174 |
- return nil |
|
| 175 |
- } |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 |
-// WithNoNewPrivileges sets no_new_privileges on the process for the container |
|
| 179 |
-func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 180 |
- setProcess(s) |
|
| 181 |
- s.Process.NoNewPrivileges = true |
|
| 182 |
- return nil |
|
| 183 |
-} |
|
| 184 |
- |
|
| 185 |
-// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly |
|
| 186 |
-func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 187 |
- s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 188 |
- Destination: "/etc/hosts", |
|
| 189 |
- Type: "bind", |
|
| 190 |
- Source: "/etc/hosts", |
|
| 191 |
- Options: []string{"rbind", "ro"},
|
|
| 192 |
- }) |
|
| 193 |
- return nil |
|
| 194 |
-} |
|
| 195 |
- |
|
| 196 |
-// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly |
|
| 197 |
-func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 198 |
- s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 199 |
- Destination: "/etc/resolv.conf", |
|
| 200 |
- Type: "bind", |
|
| 201 |
- Source: "/etc/resolv.conf", |
|
| 202 |
- Options: []string{"rbind", "ro"},
|
|
| 203 |
- }) |
|
| 204 |
- return nil |
|
| 205 |
-} |
|
| 206 |
- |
|
| 207 |
-// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly |
|
| 208 |
-func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 209 |
- s.Mounts = append(s.Mounts, specs.Mount{
|
|
| 210 |
- Destination: "/etc/localtime", |
|
| 211 |
- Type: "bind", |
|
| 212 |
- Source: "/etc/localtime", |
|
| 213 |
- Options: []string{"rbind", "ro"},
|
|
| 214 |
- }) |
|
| 215 |
- return nil |
|
| 216 |
-} |
|
| 217 |
- |
|
| 218 |
-// WithUserNamespace sets the uid and gid mappings for the task |
|
| 219 |
-// this can be called multiple times to add more mappings to the generated spec |
|
| 220 |
-func WithUserNamespace(container, host, size uint32) SpecOpts {
|
|
| 221 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 222 |
- var hasUserns bool |
|
| 223 |
- setLinux(s) |
|
| 224 |
- for _, ns := range s.Linux.Namespaces {
|
|
| 225 |
- if ns.Type == specs.UserNamespace {
|
|
| 226 |
- hasUserns = true |
|
| 227 |
- break |
|
| 228 |
- } |
|
| 229 |
- } |
|
| 230 |
- if !hasUserns {
|
|
| 231 |
- s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
|
|
| 232 |
- Type: specs.UserNamespace, |
|
| 233 |
- }) |
|
| 234 |
- } |
|
| 235 |
- mapping := specs.LinuxIDMapping{
|
|
| 236 |
- ContainerID: container, |
|
| 237 |
- HostID: host, |
|
| 238 |
- Size: size, |
|
| 239 |
- } |
|
| 240 |
- s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) |
|
| 241 |
- s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) |
|
| 242 |
- return nil |
|
| 243 |
- } |
|
| 244 |
-} |
|
| 245 |
- |
|
| 246 |
-// WithCgroup sets the container's cgroup path |
|
| 247 |
-func WithCgroup(path string) SpecOpts {
|
|
| 248 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 249 |
- setLinux(s) |
|
| 250 |
- s.Linux.CgroupsPath = path |
|
| 251 |
- return nil |
|
| 252 |
- } |
|
| 253 |
-} |
|
| 254 |
- |
|
| 255 |
-// WithNamespacedCgroup uses the namespace set on the context to create a |
|
| 256 |
-// root directory for containers in the cgroup with the id as the subcgroup |
|
| 257 |
-func WithNamespacedCgroup() SpecOpts {
|
|
| 258 |
- return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
|
| 259 |
- namespace, err := namespaces.NamespaceRequired(ctx) |
|
| 260 |
- if err != nil {
|
|
| 261 |
- return err |
|
| 262 |
- } |
|
| 263 |
- setLinux(s) |
|
| 264 |
- s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
|
| 265 |
- return nil |
|
| 266 |
- } |
|
| 267 |
-} |
|
| 268 |
- |
|
| 269 |
-// WithUser sets the user to be used within the container. |
|
| 270 |
-// It accepts a valid user string in OCI Image Spec v1.0.0: |
|
| 271 |
-// user, uid, user:group, uid:gid, uid:group, user:gid |
|
| 272 |
-func WithUser(userstr string) SpecOpts {
|
|
| 273 |
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
| 274 |
- setProcess(s) |
|
| 275 |
- parts := strings.Split(userstr, ":") |
|
| 276 |
- switch len(parts) {
|
|
| 277 |
- case 1: |
|
| 278 |
- v, err := strconv.Atoi(parts[0]) |
|
| 279 |
- if err != nil {
|
|
| 280 |
- // if we cannot parse as a uint they try to see if it is a username |
|
| 281 |
- return WithUsername(userstr)(ctx, client, c, s) |
|
| 282 |
- } |
|
| 283 |
- return WithUserID(uint32(v))(ctx, client, c, s) |
|
| 284 |
- case 2: |
|
| 285 |
- var ( |
|
| 286 |
- username string |
|
| 287 |
- groupname string |
|
| 288 |
- ) |
|
| 289 |
- var uid, gid uint32 |
|
| 290 |
- v, err := strconv.Atoi(parts[0]) |
|
| 291 |
- if err != nil {
|
|
| 292 |
- username = parts[0] |
|
| 293 |
- } else {
|
|
| 294 |
- uid = uint32(v) |
|
| 295 |
- } |
|
| 296 |
- if v, err = strconv.Atoi(parts[1]); err != nil {
|
|
| 297 |
- groupname = parts[1] |
|
| 298 |
- } else {
|
|
| 299 |
- gid = uint32(v) |
|
| 300 |
- } |
|
| 301 |
- if username == "" && groupname == "" {
|
|
| 302 |
- s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 303 |
- return nil |
|
| 304 |
- } |
|
| 305 |
- f := func(root string) error {
|
|
| 306 |
- if username != "" {
|
|
| 307 |
- uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
|
|
| 308 |
- return u.Name == username |
|
| 309 |
- }) |
|
| 310 |
- if err != nil {
|
|
| 311 |
- return err |
|
| 312 |
- } |
|
| 313 |
- } |
|
| 314 |
- if groupname != "" {
|
|
| 315 |
- gid, err = getGIDFromPath(root, func(g user.Group) bool {
|
|
| 316 |
- return g.Name == groupname |
|
| 317 |
- }) |
|
| 318 |
- if err != nil {
|
|
| 319 |
- return err |
|
| 320 |
- } |
|
| 321 |
- } |
|
| 322 |
- s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 323 |
- return nil |
|
| 324 |
- } |
|
| 325 |
- if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 326 |
- if !isRootfsAbs(s.Root.Path) {
|
|
| 327 |
- return errors.New("rootfs absolute path is required")
|
|
| 328 |
- } |
|
| 329 |
- return f(s.Root.Path) |
|
| 330 |
- } |
|
| 331 |
- if c.Snapshotter == "" {
|
|
| 332 |
- return errors.New("no snapshotter set for container")
|
|
| 333 |
- } |
|
| 334 |
- if c.SnapshotKey == "" {
|
|
| 335 |
- return errors.New("rootfs snapshot not created for container")
|
|
| 336 |
- } |
|
| 337 |
- snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 338 |
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 339 |
- if err != nil {
|
|
| 340 |
- return err |
|
| 341 |
- } |
|
| 342 |
- return mount.WithTempMount(ctx, mounts, f) |
|
| 343 |
- default: |
|
| 344 |
- return fmt.Errorf("invalid USER value %s", userstr)
|
|
| 345 |
- } |
|
| 346 |
- } |
|
| 347 |
-} |
|
| 348 |
- |
|
| 349 |
-// WithUIDGID allows the UID and GID for the Process to be set |
|
| 350 |
-func WithUIDGID(uid, gid uint32) SpecOpts {
|
|
| 351 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 352 |
- setProcess(s) |
|
| 353 |
- s.Process.User.UID = uid |
|
| 354 |
- s.Process.User.GID = gid |
|
| 355 |
- return nil |
|
| 356 |
- } |
|
| 357 |
-} |
|
| 358 |
- |
|
| 359 |
-// WithUserID sets the correct UID and GID for the container based |
|
| 360 |
-// on the image's /etc/passwd contents. If /etc/passwd does not exist, |
|
| 361 |
-// or uid is not found in /etc/passwd, it sets the requested uid, |
|
| 362 |
-// additionally sets the gid to 0, and does not return an error. |
|
| 363 |
-func WithUserID(uid uint32) SpecOpts {
|
|
| 364 |
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
|
| 365 |
- setProcess(s) |
|
| 366 |
- if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 367 |
- if !isRootfsAbs(s.Root.Path) {
|
|
| 368 |
- return errors.Errorf("rootfs absolute path is required")
|
|
| 369 |
- } |
|
| 370 |
- uuid, ugid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
|
| 371 |
- return u.Uid == int(uid) |
|
| 372 |
- }) |
|
| 373 |
- if err != nil {
|
|
| 374 |
- if os.IsNotExist(err) || err == errNoUsersFound {
|
|
| 375 |
- s.Process.User.UID, s.Process.User.GID = uid, 0 |
|
| 376 |
- return nil |
|
| 377 |
- } |
|
| 378 |
- return err |
|
| 379 |
- } |
|
| 380 |
- s.Process.User.UID, s.Process.User.GID = uuid, ugid |
|
| 381 |
- return nil |
|
| 382 |
- |
|
| 383 |
- } |
|
| 384 |
- if c.Snapshotter == "" {
|
|
| 385 |
- return errors.Errorf("no snapshotter set for container")
|
|
| 386 |
- } |
|
| 387 |
- if c.SnapshotKey == "" {
|
|
| 388 |
- return errors.Errorf("rootfs snapshot not created for container")
|
|
| 389 |
- } |
|
| 390 |
- snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 391 |
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 392 |
- if err != nil {
|
|
| 393 |
- return err |
|
| 394 |
- } |
|
| 395 |
- return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
| 396 |
- uuid, ugid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
|
| 397 |
- return u.Uid == int(uid) |
|
| 398 |
- }) |
|
| 399 |
- if err != nil {
|
|
| 400 |
- if os.IsNotExist(err) || err == errNoUsersFound {
|
|
| 401 |
- s.Process.User.UID, s.Process.User.GID = uid, 0 |
|
| 402 |
- return nil |
|
| 403 |
- } |
|
| 404 |
- return err |
|
| 405 |
- } |
|
| 406 |
- s.Process.User.UID, s.Process.User.GID = uuid, ugid |
|
| 407 |
- return nil |
|
| 408 |
- }) |
|
| 409 |
- } |
|
| 410 |
-} |
|
| 411 |
- |
|
| 412 |
-// WithUsername sets the correct UID and GID for the container |
|
| 413 |
-// based on the the image's /etc/passwd contents. If /etc/passwd |
|
| 414 |
-// does not exist, or the username is not found in /etc/passwd, |
|
| 415 |
-// it returns error. |
|
| 416 |
-func WithUsername(username string) SpecOpts {
|
|
| 417 |
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
|
|
| 418 |
- setProcess(s) |
|
| 419 |
- if c.Snapshotter == "" && c.SnapshotKey == "" {
|
|
| 420 |
- if !isRootfsAbs(s.Root.Path) {
|
|
| 421 |
- return errors.Errorf("rootfs absolute path is required")
|
|
| 422 |
- } |
|
| 423 |
- uid, gid, err := getUIDGIDFromPath(s.Root.Path, func(u user.User) bool {
|
|
| 424 |
- return u.Name == username |
|
| 425 |
- }) |
|
| 426 |
- if err != nil {
|
|
| 427 |
- return err |
|
| 428 |
- } |
|
| 429 |
- s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 430 |
- return nil |
|
| 431 |
- } |
|
| 432 |
- if c.Snapshotter == "" {
|
|
| 433 |
- return errors.Errorf("no snapshotter set for container")
|
|
| 434 |
- } |
|
| 435 |
- if c.SnapshotKey == "" {
|
|
| 436 |
- return errors.Errorf("rootfs snapshot not created for container")
|
|
| 437 |
- } |
|
| 438 |
- snapshotter := client.SnapshotService(c.Snapshotter) |
|
| 439 |
- mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey) |
|
| 440 |
- if err != nil {
|
|
| 441 |
- return err |
|
| 442 |
- } |
|
| 443 |
- return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
| 444 |
- uid, gid, err := getUIDGIDFromPath(root, func(u user.User) bool {
|
|
| 445 |
- return u.Name == username |
|
| 446 |
- }) |
|
| 447 |
- if err != nil {
|
|
| 448 |
- return err |
|
| 449 |
- } |
|
| 450 |
- s.Process.User.UID, s.Process.User.GID = uid, gid |
|
| 451 |
- return nil |
|
| 452 |
- }) |
|
| 453 |
- } |
|
| 454 |
-} |
|
| 455 |
- |
|
| 456 |
-// WithCapabilities sets Linux capabilities on the process |
|
| 457 |
-func WithCapabilities(caps []string) SpecOpts {
|
|
| 458 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 459 |
- setCapabilities(s) |
|
| 460 |
- |
|
| 461 |
- s.Process.Capabilities.Bounding = caps |
|
| 462 |
- s.Process.Capabilities.Effective = caps |
|
| 463 |
- s.Process.Capabilities.Permitted = caps |
|
| 464 |
- s.Process.Capabilities.Inheritable = caps |
|
| 465 |
- |
|
| 466 |
- return nil |
|
| 467 |
- } |
|
| 468 |
-} |
|
| 469 |
- |
|
| 470 |
-// WithAllCapabilities sets all linux capabilities for the process |
|
| 471 |
-var WithAllCapabilities = WithCapabilities(getAllCapabilities()) |
|
| 472 |
- |
|
| 473 |
-func getAllCapabilities() []string {
|
|
| 474 |
- last := capability.CAP_LAST_CAP |
|
| 475 |
- // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap |
|
| 476 |
- if last == capability.Cap(63) {
|
|
| 477 |
- last = capability.CAP_BLOCK_SUSPEND |
|
| 478 |
- } |
|
| 479 |
- var caps []string |
|
| 480 |
- for _, cap := range capability.List() {
|
|
| 481 |
- if cap > last {
|
|
| 482 |
- continue |
|
| 483 |
- } |
|
| 484 |
- caps = append(caps, "CAP_"+strings.ToUpper(cap.String())) |
|
| 485 |
- } |
|
| 486 |
- return caps |
|
| 487 |
-} |
|
| 488 |
- |
|
| 489 |
-// WithAmbientCapabilities set the Linux ambient capabilities for the process |
|
| 490 |
-// Ambient capabilities should only be set for non-root users or the caller should |
|
| 491 |
-// understand how these capabilities are used and set |
|
| 492 |
-func WithAmbientCapabilities(caps []string) SpecOpts {
|
|
| 493 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 494 |
- setCapabilities(s) |
|
| 495 |
- |
|
| 496 |
- s.Process.Capabilities.Ambient = caps |
|
| 497 |
- return nil |
|
| 498 |
- } |
|
| 499 |
-} |
|
| 500 |
- |
|
| 501 |
-var errNoUsersFound = errors.New("no users found")
|
|
| 502 |
- |
|
| 503 |
-func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint32, err error) {
|
|
| 504 |
- ppath, err := fs.RootPath(root, "/etc/passwd") |
|
| 505 |
- if err != nil {
|
|
| 506 |
- return 0, 0, err |
|
| 507 |
- } |
|
| 508 |
- users, err := user.ParsePasswdFileFilter(ppath, filter) |
|
| 509 |
- if err != nil {
|
|
| 510 |
- return 0, 0, err |
|
| 511 |
- } |
|
| 512 |
- if len(users) == 0 {
|
|
| 513 |
- return 0, 0, errNoUsersFound |
|
| 514 |
- } |
|
| 515 |
- u := users[0] |
|
| 516 |
- return uint32(u.Uid), uint32(u.Gid), nil |
|
| 517 |
-} |
|
| 518 |
- |
|
| 519 |
-var errNoGroupsFound = errors.New("no groups found")
|
|
| 520 |
- |
|
| 521 |
-func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
|
|
| 522 |
- gpath, err := fs.RootPath(root, "/etc/group") |
|
| 523 |
- if err != nil {
|
|
| 524 |
- return 0, err |
|
| 525 |
- } |
|
| 526 |
- groups, err := user.ParseGroupFileFilter(gpath, filter) |
|
| 527 |
- if err != nil {
|
|
| 528 |
- return 0, err |
|
| 529 |
- } |
|
| 530 |
- if len(groups) == 0 {
|
|
| 531 |
- return 0, errNoGroupsFound |
|
| 532 |
- } |
|
| 533 |
- g := groups[0] |
|
| 534 |
- return uint32(g.Gid), nil |
|
| 535 |
-} |
|
| 536 |
- |
|
| 537 |
-func isRootfsAbs(root string) bool {
|
|
| 538 |
- return filepath.IsAbs(root) |
|
| 539 |
-} |
|
| 540 |
- |
|
| 541 |
-// WithMaskedPaths sets the masked paths option |
|
| 542 |
-func WithMaskedPaths(paths []string) SpecOpts {
|
|
| 543 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 544 |
- setLinux(s) |
|
| 545 |
- s.Linux.MaskedPaths = paths |
|
| 546 |
- return nil |
|
| 547 |
- } |
|
| 548 |
-} |
|
| 549 |
- |
|
| 550 |
-// WithReadonlyPaths sets the read only paths option |
|
| 551 |
-func WithReadonlyPaths(paths []string) SpecOpts {
|
|
| 552 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 553 |
- setLinux(s) |
|
| 554 |
- s.Linux.ReadonlyPaths = paths |
|
| 555 |
- return nil |
|
| 556 |
- } |
|
| 557 |
-} |
|
| 558 |
- |
|
| 559 |
-// WithWriteableSysfs makes any sysfs mounts writeable |
|
| 560 |
-func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 561 |
- for i, m := range s.Mounts {
|
|
| 562 |
- if m.Type == "sysfs" {
|
|
| 563 |
- var options []string |
|
| 564 |
- for _, o := range m.Options {
|
|
| 565 |
- if o == "ro" {
|
|
| 566 |
- o = "rw" |
|
| 567 |
- } |
|
| 568 |
- options = append(options, o) |
|
| 569 |
- } |
|
| 570 |
- s.Mounts[i].Options = options |
|
| 571 |
- } |
|
| 572 |
- } |
|
| 573 |
- return nil |
|
| 574 |
-} |
|
| 575 |
- |
|
| 576 |
-// WithWriteableCgroupfs makes any cgroup mounts writeable |
|
| 577 |
-func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 578 |
- for i, m := range s.Mounts {
|
|
| 579 |
- if m.Type == "cgroup" {
|
|
| 580 |
- var options []string |
|
| 581 |
- for _, o := range m.Options {
|
|
| 582 |
- if o == "ro" {
|
|
| 583 |
- o = "rw" |
|
| 584 |
- } |
|
| 585 |
- options = append(options, o) |
|
| 586 |
- } |
|
| 587 |
- s.Mounts[i].Options = options |
|
| 588 |
- } |
|
| 589 |
- } |
|
| 590 |
- return nil |
|
| 591 |
-} |
|
| 592 |
- |
|
| 593 |
-// WithSelinuxLabel sets the process SELinux label |
|
| 594 |
-func WithSelinuxLabel(label string) SpecOpts {
|
|
| 595 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 596 |
- setProcess(s) |
|
| 597 |
- s.Process.SelinuxLabel = label |
|
| 598 |
- return nil |
|
| 599 |
- } |
|
| 600 |
-} |
|
| 601 |
- |
|
| 602 |
-// WithApparmorProfile sets the Apparmor profile for the process |
|
| 603 |
-func WithApparmorProfile(profile string) SpecOpts {
|
|
| 604 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 605 |
- setProcess(s) |
|
| 606 |
- s.Process.ApparmorProfile = profile |
|
| 607 |
- return nil |
|
| 608 |
- } |
|
| 609 |
-} |
|
| 610 |
- |
|
| 611 |
-// WithSeccompUnconfined clears the seccomp profile |
|
| 612 |
-func WithSeccompUnconfined(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 613 |
- setLinux(s) |
|
| 614 |
- s.Linux.Seccomp = nil |
|
| 615 |
- return nil |
|
| 616 |
-} |
|
| 617 |
- |
|
| 618 |
-// WithParentCgroupDevices uses the default cgroup setup to inherit the container's parent cgroup's |
|
| 619 |
-// allowed and denied devices |
|
| 620 |
-func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 621 |
- setLinux(s) |
|
| 622 |
- if s.Linux.Resources == nil {
|
|
| 623 |
- s.Linux.Resources = &specs.LinuxResources{}
|
|
| 624 |
- } |
|
| 625 |
- s.Linux.Resources.Devices = nil |
|
| 626 |
- return nil |
|
| 627 |
-} |
|
| 628 |
- |
|
| 629 |
-// WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to |
|
| 630 |
-// the container's resource cgroup spec |
|
| 631 |
-func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 632 |
- setLinux(s) |
|
| 633 |
- if s.Linux.Resources == nil {
|
|
| 634 |
- s.Linux.Resources = &specs.LinuxResources{}
|
|
| 635 |
- } |
|
| 636 |
- intptr := func(i int64) *int64 {
|
|
| 637 |
- return &i |
|
| 638 |
- } |
|
| 639 |
- s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, []specs.LinuxDeviceCgroup{
|
|
| 640 |
- {
|
|
| 641 |
- // "/dev/null", |
|
| 642 |
- Type: "c", |
|
| 643 |
- Major: intptr(1), |
|
| 644 |
- Minor: intptr(3), |
|
| 645 |
- Access: rwm, |
|
| 646 |
- Allow: true, |
|
| 647 |
- }, |
|
| 648 |
- {
|
|
| 649 |
- // "/dev/random", |
|
| 650 |
- Type: "c", |
|
| 651 |
- Major: intptr(1), |
|
| 652 |
- Minor: intptr(8), |
|
| 653 |
- Access: rwm, |
|
| 654 |
- Allow: true, |
|
| 655 |
- }, |
|
| 656 |
- {
|
|
| 657 |
- // "/dev/full", |
|
| 658 |
- Type: "c", |
|
| 659 |
- Major: intptr(1), |
|
| 660 |
- Minor: intptr(7), |
|
| 661 |
- Access: rwm, |
|
| 662 |
- Allow: true, |
|
| 663 |
- }, |
|
| 664 |
- {
|
|
| 665 |
- // "/dev/tty", |
|
| 666 |
- Type: "c", |
|
| 667 |
- Major: intptr(5), |
|
| 668 |
- Minor: intptr(0), |
|
| 669 |
- Access: rwm, |
|
| 670 |
- Allow: true, |
|
| 671 |
- }, |
|
| 672 |
- {
|
|
| 673 |
- // "/dev/zero", |
|
| 674 |
- Type: "c", |
|
| 675 |
- Major: intptr(1), |
|
| 676 |
- Minor: intptr(5), |
|
| 677 |
- Access: rwm, |
|
| 678 |
- Allow: true, |
|
| 679 |
- }, |
|
| 680 |
- {
|
|
| 681 |
- // "/dev/urandom", |
|
| 682 |
- Type: "c", |
|
| 683 |
- Major: intptr(1), |
|
| 684 |
- Minor: intptr(9), |
|
| 685 |
- Access: rwm, |
|
| 686 |
- Allow: true, |
|
| 687 |
- }, |
|
| 688 |
- {
|
|
| 689 |
- // "/dev/console", |
|
| 690 |
- Type: "c", |
|
| 691 |
- Major: intptr(5), |
|
| 692 |
- Minor: intptr(1), |
|
| 693 |
- Access: rwm, |
|
| 694 |
- Allow: true, |
|
| 695 |
- }, |
|
| 696 |
- // /dev/pts/ - pts namespaces are "coming soon" |
|
| 697 |
- {
|
|
| 698 |
- Type: "c", |
|
| 699 |
- Major: intptr(136), |
|
| 700 |
- Access: rwm, |
|
| 701 |
- Allow: true, |
|
| 702 |
- }, |
|
| 703 |
- {
|
|
| 704 |
- Type: "c", |
|
| 705 |
- Major: intptr(5), |
|
| 706 |
- Minor: intptr(2), |
|
| 707 |
- Access: rwm, |
|
| 708 |
- Allow: true, |
|
| 709 |
- }, |
|
| 710 |
- {
|
|
| 711 |
- // tuntap |
|
| 712 |
- Type: "c", |
|
| 713 |
- Major: intptr(10), |
|
| 714 |
- Minor: intptr(200), |
|
| 715 |
- Access: rwm, |
|
| 716 |
- Allow: true, |
|
| 717 |
- }, |
|
| 718 |
- }...) |
|
| 719 |
- return nil |
|
| 720 |
-} |
|
| 721 |
- |
|
| 722 |
-// WithPrivileged sets up options for a privileged container |
|
| 723 |
-// TODO(justincormack) device handling |
|
| 724 |
-var WithPrivileged = Compose( |
|
| 725 |
- WithAllCapabilities, |
|
| 726 |
- WithMaskedPaths(nil), |
|
| 727 |
- WithReadonlyPaths(nil), |
|
| 728 |
- WithWriteableSysfs, |
|
| 729 |
- WithWriteableCgroupfs, |
|
| 730 |
- WithSelinuxLabel(""),
|
|
| 731 |
- WithApparmorProfile(""),
|
|
| 732 |
- WithSeccompUnconfined, |
|
| 733 |
-) |
| 734 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,89 +0,0 @@ |
| 1 |
-// +build windows |
|
| 2 |
- |
|
| 3 |
-/* |
|
| 4 |
- Copyright The containerd Authors. |
|
| 5 |
- |
|
| 6 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 7 |
- you may not use this file except in compliance with the License. |
|
| 8 |
- You may obtain a copy of the License at |
|
| 9 |
- |
|
| 10 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 11 |
- |
|
| 12 |
- Unless required by applicable law or agreed to in writing, software |
|
| 13 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 14 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 15 |
- See the License for the specific language governing permissions and |
|
| 16 |
- limitations under the License. |
|
| 17 |
-*/ |
|
| 18 |
- |
|
| 19 |
-package oci |
|
| 20 |
- |
|
| 21 |
-import ( |
|
| 22 |
- "context" |
|
| 23 |
- "encoding/json" |
|
| 24 |
- "fmt" |
|
| 25 |
- |
|
| 26 |
- "github.com/containerd/containerd/containers" |
|
| 27 |
- "github.com/containerd/containerd/content" |
|
| 28 |
- "github.com/containerd/containerd/images" |
|
| 29 |
- "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 30 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 31 |
-) |
|
| 32 |
- |
|
| 33 |
-// WithImageConfig configures the spec to from the configuration of an Image |
|
| 34 |
-func WithImageConfig(image Image) SpecOpts {
|
|
| 35 |
- return func(ctx context.Context, client Client, _ *containers.Container, s *Spec) error {
|
|
| 36 |
- setProcess(s) |
|
| 37 |
- ic, err := image.Config(ctx) |
|
| 38 |
- if err != nil {
|
|
| 39 |
- return err |
|
| 40 |
- } |
|
| 41 |
- var ( |
|
| 42 |
- ociimage v1.Image |
|
| 43 |
- config v1.ImageConfig |
|
| 44 |
- ) |
|
| 45 |
- switch ic.MediaType {
|
|
| 46 |
- case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: |
|
| 47 |
- p, err := content.ReadBlob(ctx, image.ContentStore(), ic) |
|
| 48 |
- if err != nil {
|
|
| 49 |
- return err |
|
| 50 |
- } |
|
| 51 |
- if err := json.Unmarshal(p, &ociimage); err != nil {
|
|
| 52 |
- return err |
|
| 53 |
- } |
|
| 54 |
- config = ociimage.Config |
|
| 55 |
- default: |
|
| 56 |
- return fmt.Errorf("unknown image config media type %s", ic.MediaType)
|
|
| 57 |
- } |
|
| 58 |
- s.Process.Env = config.Env |
|
| 59 |
- s.Process.Args = append(config.Entrypoint, config.Cmd...) |
|
| 60 |
- s.Process.User = specs.User{
|
|
| 61 |
- Username: config.User, |
|
| 62 |
- } |
|
| 63 |
- return nil |
|
| 64 |
- } |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-// WithTTY sets the information on the spec as well as the environment variables for |
|
| 68 |
-// using a TTY |
|
| 69 |
-func WithTTY(width, height int) SpecOpts {
|
|
| 70 |
- return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
|
| 71 |
- setProcess(s) |
|
| 72 |
- s.Process.Terminal = true |
|
| 73 |
- if s.Process.ConsoleSize == nil {
|
|
| 74 |
- s.Process.ConsoleSize = &specs.Box{}
|
|
| 75 |
- } |
|
| 76 |
- s.Process.ConsoleSize.Width = uint(width) |
|
| 77 |
- s.Process.ConsoleSize.Height = uint(height) |
|
| 78 |
- return nil |
|
| 79 |
- } |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-// WithUsername sets the username on the process |
|
| 83 |
-func WithUsername(username string) SpecOpts {
|
|
| 84 |
- return func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
|
|
| 85 |
- setProcess(s) |
|
| 86 |
- s.Process.User.Username = username |
|
| 87 |
- return nil |
|
| 88 |
- } |
|
| 89 |
-} |
| 90 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,188 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-/* |
|
| 4 |
- Copyright The containerd Authors. |
|
| 5 |
- |
|
| 6 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 7 |
- you may not use this file except in compliance with the License. |
|
| 8 |
- You may obtain a copy of the License at |
|
| 9 |
- |
|
| 10 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 11 |
- |
|
| 12 |
- Unless required by applicable law or agreed to in writing, software |
|
| 13 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 14 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 15 |
- See the License for the specific language governing permissions and |
|
| 16 |
- limitations under the License. |
|
| 17 |
-*/ |
|
| 18 |
- |
|
| 19 |
-package oci |
|
| 20 |
- |
|
| 21 |
-import ( |
|
| 22 |
- "context" |
|
| 23 |
- "path/filepath" |
|
| 24 |
- |
|
| 25 |
- "github.com/containerd/containerd/namespaces" |
|
| 26 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 27 |
-) |
|
| 28 |
- |
|
| 29 |
-const ( |
|
| 30 |
- rwm = "rwm" |
|
| 31 |
- defaultRootfsPath = "rootfs" |
|
| 32 |
-) |
|
| 33 |
- |
|
| 34 |
-var ( |
|
| 35 |
- defaultEnv = []string{
|
|
| 36 |
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
|
| 37 |
- } |
|
| 38 |
-) |
|
| 39 |
- |
|
| 40 |
-func defaultCaps() []string {
|
|
| 41 |
- return []string{
|
|
| 42 |
- "CAP_CHOWN", |
|
| 43 |
- "CAP_DAC_OVERRIDE", |
|
| 44 |
- "CAP_FSETID", |
|
| 45 |
- "CAP_FOWNER", |
|
| 46 |
- "CAP_MKNOD", |
|
| 47 |
- "CAP_NET_RAW", |
|
| 48 |
- "CAP_SETGID", |
|
| 49 |
- "CAP_SETUID", |
|
| 50 |
- "CAP_SETFCAP", |
|
| 51 |
- "CAP_SETPCAP", |
|
| 52 |
- "CAP_NET_BIND_SERVICE", |
|
| 53 |
- "CAP_SYS_CHROOT", |
|
| 54 |
- "CAP_KILL", |
|
| 55 |
- "CAP_AUDIT_WRITE", |
|
| 56 |
- } |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 |
-func defaultNamespaces() []specs.LinuxNamespace {
|
|
| 60 |
- return []specs.LinuxNamespace{
|
|
| 61 |
- {
|
|
| 62 |
- Type: specs.PIDNamespace, |
|
| 63 |
- }, |
|
| 64 |
- {
|
|
| 65 |
- Type: specs.IPCNamespace, |
|
| 66 |
- }, |
|
| 67 |
- {
|
|
| 68 |
- Type: specs.UTSNamespace, |
|
| 69 |
- }, |
|
| 70 |
- {
|
|
| 71 |
- Type: specs.MountNamespace, |
|
| 72 |
- }, |
|
| 73 |
- {
|
|
| 74 |
- Type: specs.NetworkNamespace, |
|
| 75 |
- }, |
|
| 76 |
- } |
|
| 77 |
-} |
|
| 78 |
- |
|
| 79 |
-func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
|
| 80 |
- ns, err := namespaces.NamespaceRequired(ctx) |
|
| 81 |
- if err != nil {
|
|
| 82 |
- return err |
|
| 83 |
- } |
|
| 84 |
- |
|
| 85 |
- *s = Spec{
|
|
| 86 |
- Version: specs.Version, |
|
| 87 |
- Root: &specs.Root{
|
|
| 88 |
- Path: defaultRootfsPath, |
|
| 89 |
- }, |
|
| 90 |
- Process: &specs.Process{
|
|
| 91 |
- Env: defaultEnv, |
|
| 92 |
- Cwd: "/", |
|
| 93 |
- NoNewPrivileges: true, |
|
| 94 |
- User: specs.User{
|
|
| 95 |
- UID: 0, |
|
| 96 |
- GID: 0, |
|
| 97 |
- }, |
|
| 98 |
- Capabilities: &specs.LinuxCapabilities{
|
|
| 99 |
- Bounding: defaultCaps(), |
|
| 100 |
- Permitted: defaultCaps(), |
|
| 101 |
- Inheritable: defaultCaps(), |
|
| 102 |
- Effective: defaultCaps(), |
|
| 103 |
- }, |
|
| 104 |
- Rlimits: []specs.POSIXRlimit{
|
|
| 105 |
- {
|
|
| 106 |
- Type: "RLIMIT_NOFILE", |
|
| 107 |
- Hard: uint64(1024), |
|
| 108 |
- Soft: uint64(1024), |
|
| 109 |
- }, |
|
| 110 |
- }, |
|
| 111 |
- }, |
|
| 112 |
- Mounts: []specs.Mount{
|
|
| 113 |
- {
|
|
| 114 |
- Destination: "/proc", |
|
| 115 |
- Type: "proc", |
|
| 116 |
- Source: "proc", |
|
| 117 |
- }, |
|
| 118 |
- {
|
|
| 119 |
- Destination: "/dev", |
|
| 120 |
- Type: "tmpfs", |
|
| 121 |
- Source: "tmpfs", |
|
| 122 |
- Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
| 123 |
- }, |
|
| 124 |
- {
|
|
| 125 |
- Destination: "/dev/pts", |
|
| 126 |
- Type: "devpts", |
|
| 127 |
- Source: "devpts", |
|
| 128 |
- Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
|
| 129 |
- }, |
|
| 130 |
- {
|
|
| 131 |
- Destination: "/dev/shm", |
|
| 132 |
- Type: "tmpfs", |
|
| 133 |
- Source: "shm", |
|
| 134 |
- Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
|
| 135 |
- }, |
|
| 136 |
- {
|
|
| 137 |
- Destination: "/dev/mqueue", |
|
| 138 |
- Type: "mqueue", |
|
| 139 |
- Source: "mqueue", |
|
| 140 |
- Options: []string{"nosuid", "noexec", "nodev"},
|
|
| 141 |
- }, |
|
| 142 |
- {
|
|
| 143 |
- Destination: "/sys", |
|
| 144 |
- Type: "sysfs", |
|
| 145 |
- Source: "sysfs", |
|
| 146 |
- Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
|
| 147 |
- }, |
|
| 148 |
- {
|
|
| 149 |
- Destination: "/run", |
|
| 150 |
- Type: "tmpfs", |
|
| 151 |
- Source: "tmpfs", |
|
| 152 |
- Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
|
| 153 |
- }, |
|
| 154 |
- }, |
|
| 155 |
- Linux: &specs.Linux{
|
|
| 156 |
- MaskedPaths: []string{
|
|
| 157 |
- "/proc/acpi", |
|
| 158 |
- "/proc/kcore", |
|
| 159 |
- "/proc/keys", |
|
| 160 |
- "/proc/latency_stats", |
|
| 161 |
- "/proc/timer_list", |
|
| 162 |
- "/proc/timer_stats", |
|
| 163 |
- "/proc/sched_debug", |
|
| 164 |
- "/sys/firmware", |
|
| 165 |
- "/proc/scsi", |
|
| 166 |
- }, |
|
| 167 |
- ReadonlyPaths: []string{
|
|
| 168 |
- "/proc/asound", |
|
| 169 |
- "/proc/bus", |
|
| 170 |
- "/proc/fs", |
|
| 171 |
- "/proc/irq", |
|
| 172 |
- "/proc/sys", |
|
| 173 |
- "/proc/sysrq-trigger", |
|
| 174 |
- }, |
|
| 175 |
- CgroupsPath: filepath.Join("/", ns, id),
|
|
| 176 |
- Resources: &specs.LinuxResources{
|
|
| 177 |
- Devices: []specs.LinuxDeviceCgroup{
|
|
| 178 |
- {
|
|
| 179 |
- Allow: false, |
|
| 180 |
- Access: rwm, |
|
| 181 |
- }, |
|
| 182 |
- }, |
|
| 183 |
- }, |
|
| 184 |
- Namespaces: defaultNamespaces(), |
|
| 185 |
- }, |
|
| 186 |
- } |
|
| 187 |
- return nil |
|
| 188 |
-} |
| 189 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,44 +0,0 @@ |
| 1 |
-/* |
|
| 2 |
- Copyright The containerd Authors. |
|
| 3 |
- |
|
| 4 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 5 |
- you may not use this file except in compliance with the License. |
|
| 6 |
- You may obtain a copy of the License at |
|
| 7 |
- |
|
| 8 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 9 |
- |
|
| 10 |
- Unless required by applicable law or agreed to in writing, software |
|
| 11 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 12 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 13 |
- See the License for the specific language governing permissions and |
|
| 14 |
- limitations under the License. |
|
| 15 |
-*/ |
|
| 16 |
- |
|
| 17 |
-package oci |
|
| 18 |
- |
|
| 19 |
-import ( |
|
| 20 |
- "context" |
|
| 21 |
- |
|
| 22 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 23 |
-) |
|
| 24 |
- |
|
| 25 |
-func populateDefaultSpec(ctx context.Context, s *Spec, id string) error {
|
|
| 26 |
- *s = Spec{
|
|
| 27 |
- Version: specs.Version, |
|
| 28 |
- Root: &specs.Root{},
|
|
| 29 |
- Process: &specs.Process{
|
|
| 30 |
- Cwd: `C:\`, |
|
| 31 |
- ConsoleSize: &specs.Box{
|
|
| 32 |
- Width: 80, |
|
| 33 |
- Height: 20, |
|
| 34 |
- }, |
|
| 35 |
- }, |
|
| 36 |
- Windows: &specs.Windows{
|
|
| 37 |
- IgnoreFlushesDuringBoot: true, |
|
| 38 |
- Network: &specs.WindowsNetwork{
|
|
| 39 |
- AllowUnqualifiedDNSQuery: true, |
|
| 40 |
- }, |
|
| 41 |
- }, |
|
| 42 |
- } |
|
| 43 |
- return nil |
|
| 44 |
-} |
| ... | ... |
@@ -22,11 +22,6 @@ import ( |
| 22 | 22 |
specs "github.com/opencontainers/image-spec/specs-go/v1" |
| 23 | 23 |
) |
| 24 | 24 |
|
| 25 |
-// Default returns the default matcher for the platform. |
|
| 26 |
-func Default() MatchComparer {
|
|
| 27 |
- return Only(DefaultSpec()) |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 | 25 |
// DefaultString returns the default string specifier for the platform. |
| 31 | 26 |
func DefaultString() string {
|
| 32 | 27 |
return Format(DefaultSpec()) |
| 33 | 28 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,24 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+ Copyright The containerd Authors. |
|
| 4 |
+ |
|
| 5 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 6 |
+ you may not use this file except in compliance with the License. |
|
| 7 |
+ You may obtain a copy of the License at |
|
| 8 |
+ |
|
| 9 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 10 |
+ |
|
| 11 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 12 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 13 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 14 |
+ See the License for the specific language governing permissions and |
|
| 15 |
+ limitations under the License. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+package platforms |
|
| 19 |
+ |
|
| 20 |
+// Default returns the default matcher for the platform. |
|
| 21 |
+func Default() MatchComparer {
|
|
| 22 |
+ return Only(DefaultSpec()) |
|
| 23 |
+} |
| 0 | 24 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,31 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+ Copyright The containerd Authors. |
|
| 4 |
+ |
|
| 5 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 6 |
+ you may not use this file except in compliance with the License. |
|
| 7 |
+ You may obtain a copy of the License at |
|
| 8 |
+ |
|
| 9 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 10 |
+ |
|
| 11 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 12 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 13 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 14 |
+ See the License for the specific language governing permissions and |
|
| 15 |
+ limitations under the License. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+package platforms |
|
| 19 |
+ |
|
| 20 |
+import ( |
|
| 21 |
+ specs "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 22 |
+) |
|
| 23 |
+ |
|
| 24 |
+// Default returns the default matcher for the platform. |
|
| 25 |
+func Default() MatchComparer {
|
|
| 26 |
+ return Ordered(DefaultSpec(), specs.Platform{
|
|
| 27 |
+ OS: "linux", |
|
| 28 |
+ Architecture: "amd64", |
|
| 29 |
+ }) |
|
| 30 |
+} |
| ... | ... |
@@ -117,7 +117,7 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int |
| 117 | 117 |
} |
| 118 | 118 |
} else {
|
| 119 | 119 |
// TODO: Should any cases where use of content range |
| 120 |
- // without the proper header be considerd? |
|
| 120 |
+ // without the proper header be considered? |
|
| 121 | 121 |
// 206 responses? |
| 122 | 122 |
|
| 123 | 123 |
// Discard up to offset |
| ... | ... |
@@ -134,7 +134,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 134 | 134 |
// There is an edge case here where offset == size of the content. If |
| 135 | 135 |
// we seek, we will probably get an error for content that cannot be |
| 136 | 136 |
// sought (?). In that case, we should err on committing the content, |
| 137 |
- // as the length is already satisified but we just return the empty |
|
| 137 |
+ // as the length is already satisfied but we just return the empty |
|
| 138 | 138 |
// reader instead. |
| 139 | 139 |
|
| 140 | 140 |
hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
| ... | ... |
@@ -272,8 +272,14 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro |
| 272 | 272 |
return err |
| 273 | 273 |
} |
| 274 | 274 |
|
| 275 |
- // TODO: Check if blob -> diff id mapping already exists |
|
| 276 |
- // TODO: Check if blob empty label exists |
|
| 275 |
+ reuse, err := c.reuseLabelBlobState(ctx, desc) |
|
| 276 |
+ if err != nil {
|
|
| 277 |
+ return err |
|
| 278 |
+ } |
|
| 279 |
+ |
|
| 280 |
+ if reuse {
|
|
| 281 |
+ return nil |
|
| 282 |
+ } |
|
| 277 | 283 |
|
| 278 | 284 |
ra, err := c.contentStore.ReaderAt(ctx, desc) |
| 279 | 285 |
if err != nil {
|
| ... | ... |
@@ -343,6 +349,17 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro |
| 343 | 343 |
|
| 344 | 344 |
state := calc.State() |
| 345 | 345 |
|
| 346 |
+ cinfo := content.Info{
|
|
| 347 |
+ Digest: desc.Digest, |
|
| 348 |
+ Labels: map[string]string{
|
|
| 349 |
+ "containerd.io/uncompressed": state.diffID.String(), |
|
| 350 |
+ }, |
|
| 351 |
+ } |
|
| 352 |
+ |
|
| 353 |
+ if _, err := c.contentStore.Update(ctx, cinfo, "labels.containerd.io/uncompressed"); err != nil {
|
|
| 354 |
+ return errors.Wrap(err, "failed to update uncompressed label") |
|
| 355 |
+ } |
|
| 356 |
+ |
|
| 346 | 357 |
c.mu.Lock() |
| 347 | 358 |
c.blobMap[desc.Digest] = state |
| 348 | 359 |
c.layerBlobs[state.diffID] = desc |
| ... | ... |
@@ -351,6 +368,40 @@ func (c *Converter) fetchBlob(ctx context.Context, desc ocispec.Descriptor) erro |
| 351 | 351 |
return nil |
| 352 | 352 |
} |
| 353 | 353 |
|
| 354 |
+func (c *Converter) reuseLabelBlobState(ctx context.Context, desc ocispec.Descriptor) (bool, error) {
|
|
| 355 |
+ cinfo, err := c.contentStore.Info(ctx, desc.Digest) |
|
| 356 |
+ if err != nil {
|
|
| 357 |
+ return false, errors.Wrap(err, "failed to get blob info") |
|
| 358 |
+ } |
|
| 359 |
+ desc.Size = cinfo.Size |
|
| 360 |
+ |
|
| 361 |
+ diffID, ok := cinfo.Labels["containerd.io/uncompressed"] |
|
| 362 |
+ if !ok {
|
|
| 363 |
+ return false, nil |
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ bState := blobState{empty: false}
|
|
| 367 |
+ |
|
| 368 |
+ if bState.diffID, err = digest.Parse(diffID); err != nil {
|
|
| 369 |
+ log.G(ctx).WithField("id", desc.Digest).Warnf("failed to parse digest from label containerd.io/uncompressed: %v", diffID)
|
|
| 370 |
+ return false, nil |
|
| 371 |
+ } |
|
| 372 |
+ |
|
| 373 |
+ // NOTE: there is no need to read header to get compression method |
|
| 374 |
+ // because there are only two kinds of methods. |
|
| 375 |
+ if bState.diffID == desc.Digest {
|
|
| 376 |
+ desc.MediaType = images.MediaTypeDockerSchema2Layer |
|
| 377 |
+ } else {
|
|
| 378 |
+ desc.MediaType = images.MediaTypeDockerSchema2LayerGzip |
|
| 379 |
+ } |
|
| 380 |
+ |
|
| 381 |
+ c.mu.Lock() |
|
| 382 |
+ c.blobMap[desc.Digest] = bState |
|
| 383 |
+ c.layerBlobs[bState.diffID] = desc |
|
| 384 |
+ c.mu.Unlock() |
|
| 385 |
+ return true, nil |
|
| 386 |
+} |
|
| 387 |
+ |
|
| 354 | 388 |
func (c *Converter) schema1ManifestHistory() ([]ocispec.History, []digest.Digest, error) {
|
| 355 | 389 |
if c.pulledManifest == nil {
|
| 356 | 390 |
return nil, nil, errors.New("missing schema 1 manifest for conversion")
|
| ... | ... |
@@ -147,7 +147,7 @@ func (e *execProcess) start(ctx context.Context) (err error) {
|
| 147 | 147 |
return errors.Wrap(err, "creating new NULL IO") |
| 148 | 148 |
} |
| 149 | 149 |
} else {
|
| 150 |
- if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID); err != nil {
|
|
| 150 |
+ if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)); err != nil {
|
|
| 151 | 151 |
return errors.Wrap(err, "failed to create runc io pipes") |
| 152 | 152 |
} |
| 153 | 153 |
} |
| ... | ... |
@@ -60,11 +60,11 @@ func (s *execCreatedState) Start(ctx context.Context) error {
|
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 | 62 |
func (s *execCreatedState) Delete(ctx context.Context) error {
|
| 63 |
- s.p.mu.Lock() |
|
| 64 |
- defer s.p.mu.Unlock() |
|
| 65 | 63 |
if err := s.p.delete(ctx); err != nil {
|
| 66 | 64 |
return err |
| 67 | 65 |
} |
| 66 |
+ s.p.mu.Lock() |
|
| 67 |
+ defer s.p.mu.Unlock() |
|
| 68 | 68 |
return s.transition("deleted")
|
| 69 | 69 |
} |
| 70 | 70 |
|
| ... | ... |
@@ -168,11 +168,11 @@ func (s *execStoppedState) Start(ctx context.Context) error {
|
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 | 170 |
func (s *execStoppedState) Delete(ctx context.Context) error {
|
| 171 |
- s.p.mu.Lock() |
|
| 172 |
- defer s.p.mu.Unlock() |
|
| 173 | 171 |
if err := s.p.delete(ctx); err != nil {
|
| 174 | 172 |
return err |
| 175 | 173 |
} |
| 174 |
+ s.p.mu.Lock() |
|
| 175 |
+ defer s.p.mu.Unlock() |
|
| 176 | 176 |
return s.transition("deleted")
|
| 177 | 177 |
} |
| 178 | 178 |
|
| ... | ... |
@@ -123,7 +123,7 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
|
| 123 | 123 |
return errors.Wrap(err, "creating new NULL IO") |
| 124 | 124 |
} |
| 125 | 125 |
} else {
|
| 126 |
- if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID); err != nil {
|
|
| 126 |
+ if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID, withConditionalIO(p.stdio)); err != nil {
|
|
| 127 | 127 |
return errors.Wrap(err, "failed to create OCI runtime io pipes") |
| 128 | 128 |
} |
| 129 | 129 |
} |
| ... | ... |
@@ -228,7 +228,7 @@ func (p *Init) Status(ctx context.Context) (string, error) {
|
| 228 | 228 |
defer p.mu.Unlock() |
| 229 | 229 |
c, err := p.runtime.State(ctx, p.id) |
| 230 | 230 |
if err != nil {
|
| 231 |
- if os.IsNotExist(err) {
|
|
| 231 |
+ if strings.Contains(err.Error(), "does not exist") {
|
|
| 232 | 232 |
return "stopped", nil |
| 233 | 233 |
} |
| 234 | 234 |
return "", p.runtimeError(err, "OCI runtime state failed") |
| ... | ... |
@@ -249,7 +249,6 @@ func (p *Init) setExited(status int) {
|
| 249 | 249 |
} |
| 250 | 250 |
|
| 251 | 251 |
func (p *Init) delete(context context.Context) error {
|
| 252 |
- p.KillAll(context) |
|
| 253 | 252 |
p.wg.Wait() |
| 254 | 253 |
err := p.runtime.Delete(context, p.id, nil) |
| 255 | 254 |
// ignore errors if a runtime has already deleted the process |
| ... | ... |
@@ -400,3 +399,11 @@ func (p *Init) runtimeError(rErr error, msg string) error {
|
| 400 | 400 |
return errors.Errorf("%s: %s", msg, rMsg)
|
| 401 | 401 |
} |
| 402 | 402 |
} |
| 403 |
+ |
|
| 404 |
+func withConditionalIO(c proc.Stdio) runc.IOOpt {
|
|
| 405 |
+ return func(o *runc.IOOption) {
|
|
| 406 |
+ o.OpenStdin = c.Stdin != "" |
|
| 407 |
+ o.OpenStdout = c.Stdout != "" |
|
| 408 |
+ o.OpenStderr = c.Stderr != "" |
|
| 409 |
+ } |
|
| 410 |
+} |
| ... | ... |
@@ -109,7 +109,6 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w |
| 109 | 109 |
i.dest(fw, fr) |
| 110 | 110 |
} |
| 111 | 111 |
if stdin == "" {
|
| 112 |
- rio.Stdin().Close() |
|
| 113 | 112 |
return nil |
| 114 | 113 |
} |
| 115 | 114 |
f, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) |
| ... | ... |
@@ -26,7 +26,6 @@ import ( |
| 26 | 26 |
"path/filepath" |
| 27 | 27 |
"time" |
| 28 | 28 |
|
| 29 |
- "github.com/boltdb/bolt" |
|
| 30 | 29 |
eventstypes "github.com/containerd/containerd/api/events" |
| 31 | 30 |
"github.com/containerd/containerd/api/types" |
| 32 | 31 |
"github.com/containerd/containerd/containers" |
| ... | ... |
@@ -49,6 +48,7 @@ import ( |
| 49 | 49 |
ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 50 | 50 |
"github.com/pkg/errors" |
| 51 | 51 |
"github.com/sirupsen/logrus" |
| 52 |
+ bolt "go.etcd.io/bbolt" |
|
| 52 | 53 |
"golang.org/x/sys/unix" |
| 53 | 54 |
) |
| 54 | 55 |
|
| ... | ... |
@@ -204,7 +204,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts |
| 204 | 204 |
log.G(ctx).WithError(err).WithFields(logrus.Fields{
|
| 205 | 205 |
"id": id, |
| 206 | 206 |
"namespace": namespace, |
| 207 |
- }).Warn("failed to clen up after killed shim")
|
|
| 207 |
+ }).Warn("failed to clean up after killed shim")
|
|
| 208 | 208 |
} |
| 209 | 209 |
} |
| 210 | 210 |
shimopt = ShimRemote(r.config, r.address, cgroup, exitHandler) |
| ... | ... |
@@ -248,8 +248,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts |
| 248 | 248 |
if err != nil {
|
| 249 | 249 |
return nil, errdefs.FromGRPC(err) |
| 250 | 250 |
} |
| 251 |
- t, err := newTask(id, namespace, int(cr.Pid), s, r.events, |
|
| 252 |
- proc.NewRunc(ropts.RuntimeRoot, sopts.Bundle, namespace, rt, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle) |
|
| 251 |
+ t, err := newTask(id, namespace, int(cr.Pid), s, r.events, r.tasks, bundle) |
|
| 253 | 252 |
if err != nil {
|
| 254 | 253 |
return nil, err |
| 255 | 254 |
} |
| ... | ... |
@@ -341,15 +340,8 @@ func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
|
| 341 | 341 |
} |
| 342 | 342 |
continue |
| 343 | 343 |
} |
| 344 |
- ropts, err := r.getRuncOptions(ctx, id) |
|
| 345 |
- if err != nil {
|
|
| 346 |
- log.G(ctx).WithError(err).WithField("id", id).
|
|
| 347 |
- Error("get runtime options")
|
|
| 348 |
- continue |
|
| 349 |
- } |
|
| 350 | 344 |
|
| 351 |
- t, err := newTask(id, ns, pid, s, r.events, |
|
| 352 |
- proc.NewRunc(ropts.RuntimeRoot, bundle.path, ns, ropts.Runtime, ropts.CriuPath, ropts.SystemdCgroup), r.tasks, bundle) |
|
| 345 |
+ t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle) |
|
| 353 | 346 |
if err != nil {
|
| 354 | 347 |
log.G(ctx).WithError(err).Error("loading task type")
|
| 355 | 348 |
continue |
| ... | ... |
@@ -31,8 +31,7 @@ import ( |
| 31 | 31 |
"github.com/containerd/containerd/log" |
| 32 | 32 |
"github.com/containerd/containerd/runtime" |
| 33 | 33 |
"github.com/containerd/containerd/runtime/v1/shim/client" |
| 34 |
- shim "github.com/containerd/containerd/runtime/v1/shim/v1" |
|
| 35 |
- runc "github.com/containerd/go-runc" |
|
| 34 |
+ "github.com/containerd/containerd/runtime/v1/shim/v1" |
|
| 36 | 35 |
"github.com/containerd/ttrpc" |
| 37 | 36 |
"github.com/containerd/typeurl" |
| 38 | 37 |
"github.com/gogo/protobuf/types" |
| ... | ... |
@@ -52,7 +51,7 @@ type Task struct {
|
| 52 | 52 |
bundle *bundle |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
-func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, runtime *runc.Runc, list *runtime.TaskList, bundle *bundle) (*Task, error) {
|
|
| 55 |
+func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, list *runtime.TaskList, bundle *bundle) (*Task, error) {
|
|
| 56 | 56 |
var ( |
| 57 | 57 |
err error |
| 58 | 58 |
cg cgroups.Cgroup |
| ... | ... |
@@ -20,7 +20,9 @@ package shim |
| 20 | 20 |
|
| 21 | 21 |
import ( |
| 22 | 22 |
"context" |
| 23 |
+ "encoding/json" |
|
| 23 | 24 |
"fmt" |
| 25 |
+ "io/ioutil" |
|
| 24 | 26 |
"os" |
| 25 | 27 |
"path/filepath" |
| 26 | 28 |
"sync" |
| ... | ... |
@@ -41,6 +43,7 @@ import ( |
| 41 | 41 |
runc "github.com/containerd/go-runc" |
| 42 | 42 |
"github.com/containerd/typeurl" |
| 43 | 43 |
ptypes "github.com/gogo/protobuf/types" |
| 44 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 44 | 45 |
"github.com/pkg/errors" |
| 45 | 46 |
"github.com/sirupsen/logrus" |
| 46 | 47 |
"google.golang.org/grpc/codes" |
| ... | ... |
@@ -221,19 +224,21 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR |
| 221 | 221 |
|
| 222 | 222 |
// DeleteProcess deletes an exec'd process |
| 223 | 223 |
func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessRequest) (*shimapi.DeleteResponse, error) {
|
| 224 |
- s.mu.Lock() |
|
| 225 |
- defer s.mu.Unlock() |
|
| 226 | 224 |
if r.ID == s.id {
|
| 227 | 225 |
return nil, status.Errorf(codes.InvalidArgument, "cannot delete init process with DeleteProcess") |
| 228 | 226 |
} |
| 227 |
+ s.mu.Lock() |
|
| 229 | 228 |
p := s.processes[r.ID] |
| 229 |
+ s.mu.Unlock() |
|
| 230 | 230 |
if p == nil {
|
| 231 | 231 |
return nil, errors.Wrapf(errdefs.ErrNotFound, "process %s", r.ID) |
| 232 | 232 |
} |
| 233 | 233 |
if err := p.Delete(ctx); err != nil {
|
| 234 | 234 |
return nil, err |
| 235 | 235 |
} |
| 236 |
+ s.mu.Lock() |
|
| 236 | 237 |
delete(s.processes, r.ID) |
| 238 |
+ s.mu.Unlock() |
|
| 237 | 239 |
return &shimapi.DeleteResponse{
|
| 238 | 240 |
ExitStatus: uint32(p.ExitStatus()), |
| 239 | 241 |
ExitedAt: p.ExitedAt(), |
| ... | ... |
@@ -507,13 +512,22 @@ func (s *Service) processExits() {
|
| 507 | 507 |
func (s *Service) checkProcesses(e runc.Exit) {
|
| 508 | 508 |
s.mu.Lock() |
| 509 | 509 |
defer s.mu.Unlock() |
| 510 |
+ |
|
| 511 |
+ shouldKillAll, err := shouldKillAllOnExit(s.bundle) |
|
| 512 |
+ if err != nil {
|
|
| 513 |
+ log.G(s.context).WithError(err).Error("failed to check shouldKillAll")
|
|
| 514 |
+ } |
|
| 515 |
+ |
|
| 510 | 516 |
for _, p := range s.processes {
|
| 511 | 517 |
if p.Pid() == e.Pid {
|
| 512 |
- if ip, ok := p.(*proc.Init); ok {
|
|
| 513 |
- // Ensure all children are killed |
|
| 514 |
- if err := ip.KillAll(s.context); err != nil {
|
|
| 515 |
- log.G(s.context).WithError(err).WithField("id", ip.ID()).
|
|
| 516 |
- Error("failed to kill init's children")
|
|
| 518 |
+ |
|
| 519 |
+ if shouldKillAll {
|
|
| 520 |
+ if ip, ok := p.(*proc.Init); ok {
|
|
| 521 |
+ // Ensure all children are killed |
|
| 522 |
+ if err := ip.KillAll(s.context); err != nil {
|
|
| 523 |
+ log.G(s.context).WithError(err).WithField("id", ip.ID()).
|
|
| 524 |
+ Error("failed to kill init's children")
|
|
| 525 |
+ } |
|
| 517 | 526 |
} |
| 518 | 527 |
} |
| 519 | 528 |
p.SetExited(e.Status) |
| ... | ... |
@@ -529,6 +543,25 @@ func (s *Service) checkProcesses(e runc.Exit) {
|
| 529 | 529 |
} |
| 530 | 530 |
} |
| 531 | 531 |
|
| 532 |
+func shouldKillAllOnExit(bundlePath string) (bool, error) {
|
|
| 533 |
+ var bundleSpec specs.Spec |
|
| 534 |
+ bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json")) |
|
| 535 |
+ if err != nil {
|
|
| 536 |
+ return false, err |
|
| 537 |
+ } |
|
| 538 |
+ json.Unmarshal(bundleConfigContents, &bundleSpec) |
|
| 539 |
+ |
|
| 540 |
+ if bundleSpec.Linux != nil {
|
|
| 541 |
+ for _, ns := range bundleSpec.Linux.Namespaces {
|
|
| 542 |
+ if ns.Type == specs.PIDNamespace {
|
|
| 543 |
+ return false, nil |
|
| 544 |
+ } |
|
| 545 |
+ } |
|
| 546 |
+ } |
|
| 547 |
+ |
|
| 548 |
+ return true, nil |
|
| 549 |
+} |
|
| 550 |
+ |
|
| 532 | 551 |
func (s *Service) getContainerPids(ctx context.Context, id string) ([]uint32, error) {
|
| 533 | 552 |
s.mu.Lock() |
| 534 | 553 |
defer s.mu.Unlock() |
| ... | ... |
@@ -29,7 +29,6 @@ import ( |
| 29 | 29 |
"sync" |
| 30 | 30 |
"time" |
| 31 | 31 |
|
| 32 |
- "github.com/boltdb/bolt" |
|
| 33 | 32 |
csapi "github.com/containerd/containerd/api/services/content/v1" |
| 34 | 33 |
ssapi "github.com/containerd/containerd/api/services/snapshots/v1" |
| 35 | 34 |
"github.com/containerd/containerd/content" |
| ... | ... |
@@ -46,6 +45,7 @@ import ( |
| 46 | 46 |
metrics "github.com/docker/go-metrics" |
| 47 | 47 |
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" |
| 48 | 48 |
"github.com/pkg/errors" |
| 49 |
+ bolt "go.etcd.io/bbolt" |
|
| 49 | 50 |
"google.golang.org/grpc" |
| 50 | 51 |
) |
| 51 | 52 |
|
| ... | ... |
@@ -42,7 +42,7 @@ func CreateUnixSocket(path string) (net.Listener, error) {
|
| 42 | 42 |
return net.Listen("unix", path)
|
| 43 | 43 |
} |
| 44 | 44 |
|
| 45 |
-// GetLocalListener returns a listerner out of a unix socket. |
|
| 45 |
+// GetLocalListener returns a listener out of a unix socket. |
|
| 46 | 46 |
func GetLocalListener(path string, uid, gid int) (net.Listener, error) {
|
| 47 | 47 |
// Ensure parent directory is created |
| 48 | 48 |
if err := mkdirAs(filepath.Dir(path), uid, gid); err != nil {
|
| ... | ... |
@@ -607,8 +607,11 @@ func writeContent(ctx context.Context, store content.Ingester, mediaType, ref st |
| 607 | 607 |
if err != nil {
|
| 608 | 608 |
return d, err |
| 609 | 609 |
} |
| 610 |
+ |
|
| 610 | 611 |
if err := writer.Commit(ctx, size, "", opts...); err != nil {
|
| 611 |
- return d, err |
|
| 612 |
+ if !errdefs.IsAlreadyExists(err) {
|
|
| 613 |
+ return d, err |
|
| 614 |
+ } |
|
| 612 | 615 |
} |
| 613 | 616 |
return v1.Descriptor{
|
| 614 | 617 |
MediaType: mediaType, |
| ... | ... |
@@ -18,10 +18,18 @@ package containerd |
| 18 | 18 |
|
| 19 | 19 |
import ( |
| 20 | 20 |
"context" |
| 21 |
+ "encoding/json" |
|
| 22 |
+ "fmt" |
|
| 21 | 23 |
"syscall" |
| 22 | 24 |
|
| 25 |
+ "github.com/containerd/containerd/api/types" |
|
| 26 |
+ "github.com/containerd/containerd/content" |
|
| 23 | 27 |
"github.com/containerd/containerd/errdefs" |
| 28 |
+ "github.com/containerd/containerd/images" |
|
| 24 | 29 |
"github.com/containerd/containerd/mount" |
| 30 |
+ imagespec "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 31 |
+ "github.com/opencontainers/runtime-spec/specs-go" |
|
| 32 |
+ "github.com/pkg/errors" |
|
| 25 | 33 |
) |
| 26 | 34 |
|
| 27 | 35 |
// NewTaskOpts allows the caller to set options on a new task |
| ... | ... |
@@ -35,6 +43,44 @@ func WithRootFS(mounts []mount.Mount) NewTaskOpts {
|
| 35 | 35 |
} |
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 |
+// WithTaskCheckpoint allows a task to be created with live runtime and memory data from a |
|
| 39 |
+// previous checkpoint. Additional software such as CRIU may be required to |
|
| 40 |
+// restore a task from a checkpoint |
|
| 41 |
+func WithTaskCheckpoint(im Image) NewTaskOpts {
|
|
| 42 |
+ return func(ctx context.Context, c *Client, info *TaskInfo) error {
|
|
| 43 |
+ desc := im.Target() |
|
| 44 |
+ id := desc.Digest |
|
| 45 |
+ index, err := decodeIndex(ctx, c.ContentStore(), desc) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return err |
|
| 48 |
+ } |
|
| 49 |
+ for _, m := range index.Manifests {
|
|
| 50 |
+ if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
|
| 51 |
+ info.Checkpoint = &types.Descriptor{
|
|
| 52 |
+ MediaType: m.MediaType, |
|
| 53 |
+ Size_: m.Size, |
|
| 54 |
+ Digest: m.Digest, |
|
| 55 |
+ } |
|
| 56 |
+ return nil |
|
| 57 |
+ } |
|
| 58 |
+ } |
|
| 59 |
+ return fmt.Errorf("checkpoint not found in index %s", id)
|
|
| 60 |
+ } |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func decodeIndex(ctx context.Context, store content.Provider, desc imagespec.Descriptor) (*imagespec.Index, error) {
|
|
| 64 |
+ var index imagespec.Index |
|
| 65 |
+ p, err := content.ReadBlob(ctx, store, desc) |
|
| 66 |
+ if err != nil {
|
|
| 67 |
+ return nil, err |
|
| 68 |
+ } |
|
| 69 |
+ if err := json.Unmarshal(p, &index); err != nil {
|
|
| 70 |
+ return nil, err |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ return &index, nil |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 38 | 76 |
// WithCheckpointName sets the image name for the checkpoint |
| 39 | 77 |
func WithCheckpointName(name string) CheckpointTaskOpts {
|
| 40 | 78 |
return func(r *CheckpointTaskInfo) error {
|
| ... | ... |
@@ -92,3 +138,19 @@ func WithKillExecID(execID string) KillOpts {
|
| 92 | 92 |
return nil |
| 93 | 93 |
} |
| 94 | 94 |
} |
| 95 |
+ |
|
| 96 |
+// WithResources sets the provided resources for task updates. Resources must be |
|
| 97 |
+// either a *specs.LinuxResources or a *specs.WindowsResources |
|
| 98 |
+func WithResources(resources interface{}) UpdateTaskOpts {
|
|
| 99 |
+ return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
|
| 100 |
+ switch resources.(type) {
|
|
| 101 |
+ case *specs.LinuxResources: |
|
| 102 |
+ case *specs.WindowsResources: |
|
| 103 |
+ default: |
|
| 104 |
+ return errors.New("WithResources requires a *specs.LinuxResources or *specs.WindowsResources")
|
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ r.Resources = resources |
|
| 108 |
+ return nil |
|
| 109 |
+ } |
|
| 110 |
+} |
| 95 | 111 |
deleted file mode 100644 |
| ... | ... |
@@ -1,48 +0,0 @@ |
| 1 |
-/* |
|
| 2 |
- Copyright The containerd Authors. |
|
| 3 |
- |
|
| 4 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 5 |
- you may not use this file except in compliance with the License. |
|
| 6 |
- You may obtain a copy of the License at |
|
| 7 |
- |
|
| 8 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 9 |
- |
|
| 10 |
- Unless required by applicable law or agreed to in writing, software |
|
| 11 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 12 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 13 |
- See the License for the specific language governing permissions and |
|
| 14 |
- limitations under the License. |
|
| 15 |
-*/ |
|
| 16 |
- |
|
| 17 |
-package containerd |
|
| 18 |
- |
|
| 19 |
-import ( |
|
| 20 |
- "context" |
|
| 21 |
- "errors" |
|
| 22 |
- |
|
| 23 |
- "github.com/containerd/containerd/runtime/linux/runctypes" |
|
| 24 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 25 |
-) |
|
| 26 |
- |
|
| 27 |
-// WithResources sets the provided resources for task updates |
|
| 28 |
-func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
|
| 29 |
- return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
|
| 30 |
- r.Resources = resources |
|
| 31 |
- return nil |
|
| 32 |
- } |
|
| 33 |
-} |
|
| 34 |
- |
|
| 35 |
-// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage. |
|
| 36 |
-// There is an upper limit on the number of keyrings in a linux system |
|
| 37 |
-func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
|
| 38 |
- if ti.Options == nil {
|
|
| 39 |
- ti.Options = &runctypes.CreateOptions{}
|
|
| 40 |
- } |
|
| 41 |
- opts, ok := ti.Options.(*runctypes.CreateOptions) |
|
| 42 |
- if !ok {
|
|
| 43 |
- return errors.New("could not cast TaskInfo Options to CreateOptions")
|
|
| 44 |
- } |
|
| 45 |
- |
|
| 46 |
- opts.NoNewKeyring = true |
|
| 47 |
- return nil |
|
| 48 |
-} |
| 49 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+ Copyright The containerd Authors. |
|
| 4 |
+ |
|
| 5 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 6 |
+ you may not use this file except in compliance with the License. |
|
| 7 |
+ You may obtain a copy of the License at |
|
| 8 |
+ |
|
| 9 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 10 |
+ |
|
| 11 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 12 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 13 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 14 |
+ See the License for the specific language governing permissions and |
|
| 15 |
+ limitations under the License. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+package containerd |
|
| 19 |
+ |
|
| 20 |
+import ( |
|
| 21 |
+ "context" |
|
| 22 |
+ |
|
| 23 |
+ "github.com/containerd/containerd/runtime/linux/runctypes" |
|
| 24 |
+ "github.com/pkg/errors" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage. |
|
| 28 |
+// There is an upper limit on the number of keyrings in a linux system |
|
| 29 |
+func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error {
|
|
| 30 |
+ if ti.Options == nil {
|
|
| 31 |
+ ti.Options = &runctypes.CreateOptions{}
|
|
| 32 |
+ } |
|
| 33 |
+ opts, ok := ti.Options.(*runctypes.CreateOptions) |
|
| 34 |
+ if !ok {
|
|
| 35 |
+ return errors.New("could not cast TaskInfo Options to CreateOptions")
|
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ opts.NoNewKeyring = true |
|
| 39 |
+ return nil |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// WithNoPivotRoot instructs the runtime not to you pivot_root |
|
| 43 |
+func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
|
|
| 44 |
+ if info.Options == nil {
|
|
| 45 |
+ info.Options = &runctypes.CreateOptions{
|
|
| 46 |
+ NoPivotRoot: true, |
|
| 47 |
+ } |
|
| 48 |
+ return nil |
|
| 49 |
+ } |
|
| 50 |
+ opts, ok := info.Options.(*runctypes.CreateOptions) |
|
| 51 |
+ if !ok {
|
|
| 52 |
+ return errors.New("invalid options type, expected runctypes.CreateOptions")
|
|
| 53 |
+ } |
|
| 54 |
+ opts.NoPivotRoot = true |
|
| 55 |
+ return nil |
|
| 56 |
+} |
| 0 | 57 |
deleted file mode 100644 |
| ... | ... |
@@ -1,31 +0,0 @@ |
| 1 |
-/* |
|
| 2 |
- Copyright The containerd Authors. |
|
| 3 |
- |
|
| 4 |
- Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 5 |
- you may not use this file except in compliance with the License. |
|
| 6 |
- You may obtain a copy of the License at |
|
| 7 |
- |
|
| 8 |
- http://www.apache.org/licenses/LICENSE-2.0 |
|
| 9 |
- |
|
| 10 |
- Unless required by applicable law or agreed to in writing, software |
|
| 11 |
- distributed under the License is distributed on an "AS IS" BASIS, |
|
| 12 |
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 13 |
- See the License for the specific language governing permissions and |
|
| 14 |
- limitations under the License. |
|
| 15 |
-*/ |
|
| 16 |
- |
|
| 17 |
-package containerd |
|
| 18 |
- |
|
| 19 |
-import ( |
|
| 20 |
- "context" |
|
| 21 |
- |
|
| 22 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 23 |
-) |
|
| 24 |
- |
|
| 25 |
-// WithResources sets the provided resources on the spec for task updates |
|
| 26 |
-func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
|
| 27 |
- return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
|
| 28 |
- r.Resources = resources |
|
| 29 |
- return nil |
|
| 30 |
- } |
|
| 31 |
-} |
| ... | ... |
@@ -1,10 +1,10 @@ |
| 1 |
-github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292 |
|
| 1 |
+github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 |
|
| 2 | 2 |
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 |
| 3 | 3 |
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 |
| 4 | 4 |
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 |
| 5 | 5 |
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c |
| 6 | 6 |
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244 |
| 7 |
-github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b |
|
| 7 |
+github.com/containerd/continuity f44b615e492bdfb371aae2f76ec694d9da1db537 |
|
| 8 | 8 |
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6 |
| 9 | 9 |
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098 |
| 10 | 10 |
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 |
| ... | ... |
@@ -19,7 +19,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0 |
| 19 | 19 |
github.com/gogo/protobuf v1.0.0 |
| 20 | 20 |
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef |
| 21 | 21 |
github.com/golang/protobuf v1.1.0 |
| 22 |
-github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce # v1.0.1-43-gd810dbc |
|
| 22 |
+github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d |
|
| 23 | 23 |
github.com/opencontainers/runc 20aff4f0488c6d4b8df4d85b4f63f1f704c11abd |
| 24 | 24 |
github.com/sirupsen/logrus v1.0.0 |
| 25 | 25 |
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c |
| ... | ... |
@@ -34,17 +34,17 @@ github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 |
| 34 | 34 |
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 |
| 35 | 35 |
github.com/Microsoft/go-winio v0.4.10 |
| 36 | 36 |
github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55 |
| 37 |
-github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd |
|
| 38 | 37 |
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 |
| 39 | 38 |
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 |
| 40 | 39 |
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d |
| 41 | 40 |
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 |
| 42 | 41 |
gotest.tools v2.1.0 |
| 43 | 42 |
github.com/google/go-cmp v0.1.0 |
| 43 |
+go.etcd.io/bbolt v1.3.1-etcd.8 |
|
| 44 | 44 |
|
| 45 | 45 |
# cri dependencies |
| 46 |
-github.com/containerd/cri v1.11.1 |
|
| 47 |
-github.com/containerd/go-cni 5882530828ecf62032409b298a3e8b19e08b6534 |
|
| 46 |
+github.com/containerd/cri 9f39e3289533fc228c5e5fcac0a6dbdd60c6047b # release/1.2 branch |
|
| 47 |
+github.com/containerd/go-cni 6d7b509a054a3cb1c35ed1865d4fde2f0cb547cd |
|
| 48 | 48 |
github.com/blang/semver v3.1.0 |
| 49 | 49 |
github.com/containernetworking/cni v0.6.0 |
| 50 | 50 |
github.com/containernetworking/plugins v0.7.0 |
| ... | ... |
@@ -52,32 +52,33 @@ github.com/davecgh/go-spew v1.1.0 |
| 52 | 52 |
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 |
| 53 | 53 |
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 |
| 54 | 54 |
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528 |
| 55 |
-github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46 |
|
| 56 |
-github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee |
|
| 55 |
+github.com/emicklei/go-restful v2.2.1 |
|
| 56 |
+github.com/ghodss/yaml v1.0.0 |
|
| 57 | 57 |
github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed |
| 58 | 58 |
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c |
| 59 | 59 |
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55 |
| 60 | 60 |
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f |
| 61 |
-github.com/json-iterator/go f2b4162afba35581b6d4a50d3b8f34e33c144682 |
|
| 62 |
-github.com/modern-go/reflect2 05fbef0ca5da472bbf96c9322b84a53edc03c9fd |
|
| 61 |
+github.com/json-iterator/go 1.1.5 |
|
| 62 |
+github.com/modern-go/reflect2 1.0.1 |
|
| 63 | 63 |
github.com/modern-go/concurrent 1.0.3 |
| 64 | 64 |
github.com/opencontainers/runtime-tools v0.6.0 |
| 65 |
-github.com/opencontainers/selinux 4a2974bf1ee960774ffd517717f1f45325af0206 |
|
| 65 |
+github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a |
|
| 66 | 66 |
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 |
| 67 |
-github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc |
|
| 67 |
+github.com/tchap/go-patricia v2.2.6 |
|
| 68 | 68 |
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6 |
| 69 | 69 |
github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b |
| 70 | 70 |
github.com/xeipuuv/gojsonschema 1d523034197ff1f222f6429836dd36a2457a1874 |
| 71 | 71 |
golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067 |
| 72 |
+golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4 |
|
| 72 | 73 |
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 |
| 73 | 74 |
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 |
| 74 |
-gopkg.in/yaml.v2 53feefa2559fb8dfa8d81baad31be332c97d6c77 |
|
| 75 |
-k8s.io/api 9e5ffd1f1320950b238cfce291b926411f0af722 |
|
| 76 |
-k8s.io/apimachinery ed135c5b96450fd24e5e981c708114fbbd950697 |
|
| 77 |
-k8s.io/apiserver a90e3a95c2e91b944bfca8225c4e0d12e42a9eb5 |
|
| 78 |
-k8s.io/client-go 03bfb9bdcfe5482795b999f39ca3ed9ad42ce5bb |
|
| 79 |
-k8s.io/kubernetes v1.11.0 |
|
| 80 |
-k8s.io/utils 733eca437aa39379e4bcc25e726439dfca40fcff |
|
| 75 |
+gopkg.in/yaml.v2 v2.2.1 |
|
| 76 |
+k8s.io/api 012f271b5d41baad56190c5f1ae19bff16df0fd8 |
|
| 77 |
+k8s.io/apimachinery 6429050ef506887d121f3e7306e894f8900d8a63 |
|
| 78 |
+k8s.io/apiserver e9312c15296b6c2c923ebd5031ff5d1d5fd022d7 |
|
| 79 |
+k8s.io/client-go 37c3c02ec96533daec0dbda1f39a6b1d68505c79 |
|
| 80 |
+k8s.io/kubernetes v1.12.0-beta.1 |
|
| 81 |
+k8s.io/utils 982821ea41da7e7c15f3d3738921eb2e7e241ccd |
|
| 81 | 82 |
|
| 82 | 83 |
# zfs dependencies |
| 83 | 84 |
github.com/containerd/zfs 9a0b8b8b5982014b729cd34eb7cd7a11062aa6ec |
| ... | ... |
@@ -85,4 +86,4 @@ github.com/mistifyio/go-zfs 166add352731e515512690329794ee593f1aaff2 |
| 85 | 85 |
github.com/pborman/uuid c65b2f87fee37d1c7854c9164a450713c28d50cd |
| 86 | 86 |
|
| 87 | 87 |
# aufs dependencies |
| 88 |
-github.com/containerd/aufs a7fbd554da7a9eafbe5a460a421313a9fd18d988 |
|
| 88 |
+github.com/containerd/aufs ffa39970e26ad01d81f540b21e65f9c1841a5f92 |
| 89 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,657 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "log" |
|
| 7 |
+ "os" |
|
| 8 |
+ "path/filepath" |
|
| 9 |
+ "strings" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/containerd/continuity/devices" |
|
| 12 |
+ driverpkg "github.com/containerd/continuity/driver" |
|
| 13 |
+ "github.com/containerd/continuity/pathdriver" |
|
| 14 |
+ |
|
| 15 |
+ "github.com/opencontainers/go-digest" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+var ( |
|
| 19 |
+ // ErrNotFound represents the resource not found |
|
| 20 |
+ ErrNotFound = fmt.Errorf("not found")
|
|
| 21 |
+ // ErrNotSupported represents the resource not supported |
|
| 22 |
+ ErrNotSupported = fmt.Errorf("not supported")
|
|
| 23 |
+) |
|
| 24 |
+ |
|
| 25 |
+// Context represents a file system context for accessing resources. The |
|
| 26 |
+// responsibility of the context is to convert system specific resources to |
|
| 27 |
+// generic Resource objects. Most of this is safe path manipulation, as well |
|
| 28 |
+// as extraction of resource details. |
|
| 29 |
+type Context interface {
|
|
| 30 |
+ Apply(Resource) error |
|
| 31 |
+ Verify(Resource) error |
|
| 32 |
+ Resource(string, os.FileInfo) (Resource, error) |
|
| 33 |
+ Walk(filepath.WalkFunc) error |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// SymlinkPath is intended to give the symlink target value |
|
| 37 |
+// in a root context. Target and linkname are absolute paths |
|
| 38 |
+// not under the given root. |
|
| 39 |
+type SymlinkPath func(root, linkname, target string) (string, error) |
|
| 40 |
+ |
|
| 41 |
+// ContextOptions represents options to create a new context. |
|
| 42 |
+type ContextOptions struct {
|
|
| 43 |
+ Digester Digester |
|
| 44 |
+ Driver driverpkg.Driver |
|
| 45 |
+ PathDriver pathdriver.PathDriver |
|
| 46 |
+ Provider ContentProvider |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+// context represents a file system context for accessing resources. |
|
| 50 |
+// Generally, all path qualified access and system considerations should land |
|
| 51 |
+// here. |
|
| 52 |
+type context struct {
|
|
| 53 |
+ driver driverpkg.Driver |
|
| 54 |
+ pathDriver pathdriver.PathDriver |
|
| 55 |
+ root string |
|
| 56 |
+ digester Digester |
|
| 57 |
+ provider ContentProvider |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// NewContext returns a Context associated with root. The default driver will |
|
| 61 |
+// be used, as returned by NewDriver. |
|
| 62 |
+func NewContext(root string) (Context, error) {
|
|
| 63 |
+ return NewContextWithOptions(root, ContextOptions{})
|
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// NewContextWithOptions returns a Context associate with the root. |
|
| 67 |
+func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
|
|
| 68 |
+ // normalize to absolute path |
|
| 69 |
+ pathDriver := options.PathDriver |
|
| 70 |
+ if pathDriver == nil {
|
|
| 71 |
+ pathDriver = pathdriver.LocalPathDriver |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ root = pathDriver.FromSlash(root) |
|
| 75 |
+ root, err := pathDriver.Abs(pathDriver.Clean(root)) |
|
| 76 |
+ if err != nil {
|
|
| 77 |
+ return nil, err |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ driver := options.Driver |
|
| 81 |
+ if driver == nil {
|
|
| 82 |
+ driver, err = driverpkg.NewSystemDriver() |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return nil, err |
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ digester := options.Digester |
|
| 89 |
+ if digester == nil {
|
|
| 90 |
+ digester = simpleDigester{digest.Canonical}
|
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ // Check the root directory. Need to be a little careful here. We are |
|
| 94 |
+ // allowing a link for now, but this may have odd behavior when |
|
| 95 |
+ // canonicalizing paths. As long as all files are opened through the link |
|
| 96 |
+ // path, this should be okay. |
|
| 97 |
+ fi, err := driver.Stat(root) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ return nil, err |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ if !fi.IsDir() {
|
|
| 103 |
+ return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
|
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ return &context{
|
|
| 107 |
+ root: root, |
|
| 108 |
+ driver: driver, |
|
| 109 |
+ pathDriver: pathDriver, |
|
| 110 |
+ digester: digester, |
|
| 111 |
+ provider: options.Provider, |
|
| 112 |
+ }, nil |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+// Resource returns the resource as path p, populating the entry with info |
|
| 116 |
+// from fi. The path p should be the path of the resource in the context, |
|
| 117 |
+// typically obtained through Walk or from the value of Resource.Path(). If fi |
|
| 118 |
+// is nil, it will be resolved. |
|
| 119 |
+func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
|
|
| 120 |
+ fp, err := c.fullpath(p) |
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ return nil, err |
|
| 123 |
+ } |
|
| 124 |
+ |
|
| 125 |
+ if fi == nil {
|
|
| 126 |
+ fi, err = c.driver.Lstat(fp) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return nil, err |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ base, err := newBaseResource(p, fi) |
|
| 133 |
+ if err != nil {
|
|
| 134 |
+ return nil, err |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ base.xattrs, err = c.resolveXAttrs(fp, fi, base) |
|
| 138 |
+ if err == ErrNotSupported {
|
|
| 139 |
+ log.Printf("resolving xattrs on %s not supported", fp)
|
|
| 140 |
+ } else if err != nil {
|
|
| 141 |
+ return nil, err |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ // TODO(stevvooe): Handle windows alternate data streams. |
|
| 145 |
+ |
|
| 146 |
+ if fi.Mode().IsRegular() {
|
|
| 147 |
+ dgst, err := c.digest(p) |
|
| 148 |
+ if err != nil {
|
|
| 149 |
+ return nil, err |
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ return newRegularFile(*base, base.paths, fi.Size(), dgst) |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ if fi.Mode().IsDir() {
|
|
| 156 |
+ return newDirectory(*base) |
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ if fi.Mode()&os.ModeSymlink != 0 {
|
|
| 160 |
+ // We handle relative links vs absolute links by including a |
|
| 161 |
+ // beginning slash for absolute links. Effectively, the bundle's |
|
| 162 |
+ // root is treated as the absolute link anchor. |
|
| 163 |
+ target, err := c.driver.Readlink(fp) |
|
| 164 |
+ if err != nil {
|
|
| 165 |
+ return nil, err |
|
| 166 |
+ } |
|
| 167 |
+ |
|
| 168 |
+ return newSymLink(*base, target) |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ if fi.Mode()&os.ModeNamedPipe != 0 {
|
|
| 172 |
+ return newNamedPipe(*base, base.paths) |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ if fi.Mode()&os.ModeDevice != 0 {
|
|
| 176 |
+ deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver) |
|
| 177 |
+ if !ok {
|
|
| 178 |
+ log.Printf("device extraction not supported %s", fp)
|
|
| 179 |
+ return nil, ErrNotSupported |
|
| 180 |
+ } |
|
| 181 |
+ |
|
| 182 |
+ // character and block devices merely need to recover the |
|
| 183 |
+ // major/minor device number. |
|
| 184 |
+ major, minor, err := deviceDriver.DeviceInfo(fi) |
|
| 185 |
+ if err != nil {
|
|
| 186 |
+ return nil, err |
|
| 187 |
+ } |
|
| 188 |
+ |
|
| 189 |
+ return newDevice(*base, base.paths, major, minor) |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ log.Printf("%q (%v) is not supported", fp, fi.Mode())
|
|
| 193 |
+ return nil, ErrNotFound |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+func (c *context) verifyMetadata(resource, target Resource) error {
|
|
| 197 |
+ if target.Mode() != resource.Mode() {
|
|
| 198 |
+ return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
|
|
| 199 |
+ } |
|
| 200 |
+ |
|
| 201 |
+ if target.UID() != resource.UID() {
|
|
| 202 |
+ return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
|
|
| 203 |
+ } |
|
| 204 |
+ |
|
| 205 |
+ if target.GID() != resource.GID() {
|
|
| 206 |
+ return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
|
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ if xattrer, ok := resource.(XAttrer); ok {
|
|
| 210 |
+ txattrer, tok := target.(XAttrer) |
|
| 211 |
+ if !tok {
|
|
| 212 |
+ return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
|
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 215 |
+ // For xattrs, only ensure that we have those defined in the resource |
|
| 216 |
+ // and their values match. We can ignore other xattrs. In other words, |
|
| 217 |
+ // we only verify that target has the subset defined by resource. |
|
| 218 |
+ txattrs := txattrer.XAttrs() |
|
| 219 |
+ for attr, value := range xattrer.XAttrs() {
|
|
| 220 |
+ tvalue, ok := txattrs[attr] |
|
| 221 |
+ if !ok {
|
|
| 222 |
+ return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
|
|
| 223 |
+ } |
|
| 224 |
+ |
|
| 225 |
+ if !bytes.Equal(value, tvalue) {
|
|
| 226 |
+ return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
|
|
| 227 |
+ } |
|
| 228 |
+ } |
|
| 229 |
+ } |
|
| 230 |
+ |
|
| 231 |
+ switch r := resource.(type) {
|
|
| 232 |
+ case RegularFile: |
|
| 233 |
+ // TODO(stevvooe): Another reason to use a record-based approach. We |
|
| 234 |
+ // have to do another type switch to get this to work. This could be |
|
| 235 |
+ // fixed with an Equal function, but let's study this a little more to |
|
| 236 |
+ // be sure. |
|
| 237 |
+ t, ok := target.(RegularFile) |
|
| 238 |
+ if !ok {
|
|
| 239 |
+ return fmt.Errorf("resource %q target not a regular file", r.Path())
|
|
| 240 |
+ } |
|
| 241 |
+ |
|
| 242 |
+ if t.Size() != r.Size() {
|
|
| 243 |
+ return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
|
|
| 244 |
+ } |
|
| 245 |
+ case Directory: |
|
| 246 |
+ t, ok := target.(Directory) |
|
| 247 |
+ if !ok {
|
|
| 248 |
+ return fmt.Errorf("resource %q target not a directory", t.Path())
|
|
| 249 |
+ } |
|
| 250 |
+ case SymLink: |
|
| 251 |
+ t, ok := target.(SymLink) |
|
| 252 |
+ if !ok {
|
|
| 253 |
+ return fmt.Errorf("resource %q target not a symlink", t.Path())
|
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ if t.Target() != r.Target() {
|
|
| 257 |
+ return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
|
|
| 258 |
+ } |
|
| 259 |
+ case Device: |
|
| 260 |
+ t, ok := target.(Device) |
|
| 261 |
+ if !ok {
|
|
| 262 |
+ return fmt.Errorf("resource %q is not a device", t.Path())
|
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ if t.Major() != r.Major() || t.Minor() != r.Minor() {
|
|
| 266 |
+ return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
|
|
| 267 |
+ } |
|
| 268 |
+ case NamedPipe: |
|
| 269 |
+ t, ok := target.(NamedPipe) |
|
| 270 |
+ if !ok {
|
|
| 271 |
+ return fmt.Errorf("resource %q is not a named pipe", t.Path())
|
|
| 272 |
+ } |
|
| 273 |
+ default: |
|
| 274 |
+ return fmt.Errorf("cannot verify resource: %v", resource)
|
|
| 275 |
+ } |
|
| 276 |
+ |
|
| 277 |
+ return nil |
|
| 278 |
+} |
|
| 279 |
+ |
|
| 280 |
+// Verify the resource in the context. An error will be returned a discrepancy |
|
| 281 |
+// is found. |
|
| 282 |
+func (c *context) Verify(resource Resource) error {
|
|
| 283 |
+ fp, err := c.fullpath(resource.Path()) |
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ return err |
|
| 286 |
+ } |
|
| 287 |
+ |
|
| 288 |
+ fi, err := c.driver.Lstat(fp) |
|
| 289 |
+ if err != nil {
|
|
| 290 |
+ return err |
|
| 291 |
+ } |
|
| 292 |
+ |
|
| 293 |
+ target, err := c.Resource(resource.Path(), fi) |
|
| 294 |
+ if err != nil {
|
|
| 295 |
+ return err |
|
| 296 |
+ } |
|
| 297 |
+ |
|
| 298 |
+ if target.Path() != resource.Path() {
|
|
| 299 |
+ return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
|
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ if err := c.verifyMetadata(resource, target); err != nil {
|
|
| 303 |
+ return err |
|
| 304 |
+ } |
|
| 305 |
+ |
|
| 306 |
+ if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
|
| 307 |
+ hardlinkKey, err := newHardlinkKey(fi) |
|
| 308 |
+ if err == errNotAHardLink {
|
|
| 309 |
+ if len(h.Paths()) > 1 {
|
|
| 310 |
+ return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
|
|
| 311 |
+ } |
|
| 312 |
+ } else if err != nil {
|
|
| 313 |
+ return err |
|
| 314 |
+ } |
|
| 315 |
+ |
|
| 316 |
+ for _, path := range h.Paths()[1:] {
|
|
| 317 |
+ fpLink, err := c.fullpath(path) |
|
| 318 |
+ if err != nil {
|
|
| 319 |
+ return err |
|
| 320 |
+ } |
|
| 321 |
+ |
|
| 322 |
+ fiLink, err := c.driver.Lstat(fpLink) |
|
| 323 |
+ if err != nil {
|
|
| 324 |
+ return err |
|
| 325 |
+ } |
|
| 326 |
+ |
|
| 327 |
+ targetLink, err := c.Resource(path, fiLink) |
|
| 328 |
+ if err != nil {
|
|
| 329 |
+ return err |
|
| 330 |
+ } |
|
| 331 |
+ |
|
| 332 |
+ hardlinkKeyLink, err := newHardlinkKey(fiLink) |
|
| 333 |
+ if err != nil {
|
|
| 334 |
+ return err |
|
| 335 |
+ } |
|
| 336 |
+ |
|
| 337 |
+ if hardlinkKeyLink != hardlinkKey {
|
|
| 338 |
+ return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
|
|
| 339 |
+ } |
|
| 340 |
+ |
|
| 341 |
+ if err := c.verifyMetadata(resource, targetLink); err != nil {
|
|
| 342 |
+ return err |
|
| 343 |
+ } |
|
| 344 |
+ } |
|
| 345 |
+ } |
|
| 346 |
+ |
|
| 347 |
+ switch r := resource.(type) {
|
|
| 348 |
+ case RegularFile: |
|
| 349 |
+ t, ok := target.(RegularFile) |
|
| 350 |
+ if !ok {
|
|
| 351 |
+ return fmt.Errorf("resource %q target not a regular file", r.Path())
|
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ // TODO(stevvooe): This may need to get a little more sophisticated |
|
| 355 |
+ // for digest comparison. We may want to actually calculate the |
|
| 356 |
+ // provided digests, rather than the implementations having an |
|
| 357 |
+ // overlap. |
|
| 358 |
+ if !digestsMatch(t.Digests(), r.Digests()) {
|
|
| 359 |
+ return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
|
|
| 360 |
+ } |
|
| 361 |
+ } |
|
| 362 |
+ |
|
| 363 |
+ return nil |
|
| 364 |
+} |
|
| 365 |
+ |
|
| 366 |
+func (c *context) checkoutFile(fp string, rf RegularFile) error {
|
|
| 367 |
+ if c.provider == nil {
|
|
| 368 |
+ return fmt.Errorf("no file provider")
|
|
| 369 |
+ } |
|
| 370 |
+ var ( |
|
| 371 |
+ r io.ReadCloser |
|
| 372 |
+ err error |
|
| 373 |
+ ) |
|
| 374 |
+ for _, dgst := range rf.Digests() {
|
|
| 375 |
+ r, err = c.provider.Reader(dgst) |
|
| 376 |
+ if err == nil {
|
|
| 377 |
+ break |
|
| 378 |
+ } |
|
| 379 |
+ } |
|
| 380 |
+ if err != nil {
|
|
| 381 |
+ return fmt.Errorf("file content could not be provided: %v", err)
|
|
| 382 |
+ } |
|
| 383 |
+ defer r.Close() |
|
| 384 |
+ |
|
| 385 |
+ return atomicWriteFile(fp, r, rf.Size(), rf.Mode()) |
|
| 386 |
+} |
|
| 387 |
+ |
|
| 388 |
+// Apply the resource to the contexts. An error will be returned if the |
|
| 389 |
+// operation fails. Depending on the resource type, the resource may be |
|
| 390 |
+// created. For resource that cannot be resolved, an error will be returned. |
|
| 391 |
+func (c *context) Apply(resource Resource) error {
|
|
| 392 |
+ fp, err := c.fullpath(resource.Path()) |
|
| 393 |
+ if err != nil {
|
|
| 394 |
+ return err |
|
| 395 |
+ } |
|
| 396 |
+ |
|
| 397 |
+ if !strings.HasPrefix(fp, c.root) {
|
|
| 398 |
+ return fmt.Errorf("resource %v escapes root", resource)
|
|
| 399 |
+ } |
|
| 400 |
+ |
|
| 401 |
+ var chmod = true |
|
| 402 |
+ fi, err := c.driver.Lstat(fp) |
|
| 403 |
+ if err != nil {
|
|
| 404 |
+ if !os.IsNotExist(err) {
|
|
| 405 |
+ return err |
|
| 406 |
+ } |
|
| 407 |
+ } |
|
| 408 |
+ |
|
| 409 |
+ switch r := resource.(type) {
|
|
| 410 |
+ case RegularFile: |
|
| 411 |
+ if fi == nil {
|
|
| 412 |
+ if err := c.checkoutFile(fp, r); err != nil {
|
|
| 413 |
+ return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
|
| 414 |
+ } |
|
| 415 |
+ chmod = false |
|
| 416 |
+ } else {
|
|
| 417 |
+ if !fi.Mode().IsRegular() {
|
|
| 418 |
+ return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
|
|
| 419 |
+ } |
|
| 420 |
+ if fi.Size() != r.Size() {
|
|
| 421 |
+ if err := c.checkoutFile(fp, r); err != nil {
|
|
| 422 |
+ return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
|
| 423 |
+ } |
|
| 424 |
+ } else {
|
|
| 425 |
+ for _, dgst := range r.Digests() {
|
|
| 426 |
+ f, err := os.Open(fp) |
|
| 427 |
+ if err != nil {
|
|
| 428 |
+ return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
|
|
| 429 |
+ } |
|
| 430 |
+ compared, err := dgst.Algorithm().FromReader(f) |
|
| 431 |
+ if err == nil && dgst != compared {
|
|
| 432 |
+ if err := c.checkoutFile(fp, r); err != nil {
|
|
| 433 |
+ return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
|
| 434 |
+ } |
|
| 435 |
+ break |
|
| 436 |
+ } |
|
| 437 |
+ if err1 := f.Close(); err == nil {
|
|
| 438 |
+ err = err1 |
|
| 439 |
+ } |
|
| 440 |
+ if err != nil {
|
|
| 441 |
+ return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
|
|
| 442 |
+ } |
|
| 443 |
+ } |
|
| 444 |
+ } |
|
| 445 |
+ } |
|
| 446 |
+ case Directory: |
|
| 447 |
+ if fi == nil {
|
|
| 448 |
+ if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
|
|
| 449 |
+ return err |
|
| 450 |
+ } |
|
| 451 |
+ } else if !fi.Mode().IsDir() {
|
|
| 452 |
+ return fmt.Errorf("%q should be a directory, but is not", resource.Path())
|
|
| 453 |
+ } |
|
| 454 |
+ |
|
| 455 |
+ case SymLink: |
|
| 456 |
+ var target string // only possibly set if target resource is a symlink |
|
| 457 |
+ |
|
| 458 |
+ if fi != nil {
|
|
| 459 |
+ if fi.Mode()&os.ModeSymlink != 0 {
|
|
| 460 |
+ target, err = c.driver.Readlink(fp) |
|
| 461 |
+ if err != nil {
|
|
| 462 |
+ return err |
|
| 463 |
+ } |
|
| 464 |
+ } |
|
| 465 |
+ } |
|
| 466 |
+ |
|
| 467 |
+ if target != r.Target() {
|
|
| 468 |
+ if fi != nil {
|
|
| 469 |
+ if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
|
|
| 470 |
+ return err |
|
| 471 |
+ } |
|
| 472 |
+ } |
|
| 473 |
+ |
|
| 474 |
+ if err := c.driver.Symlink(r.Target(), fp); err != nil {
|
|
| 475 |
+ return err |
|
| 476 |
+ } |
|
| 477 |
+ } |
|
| 478 |
+ |
|
| 479 |
+ case Device: |
|
| 480 |
+ if fi == nil {
|
|
| 481 |
+ if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
|
| 482 |
+ return err |
|
| 483 |
+ } |
|
| 484 |
+ } else if (fi.Mode() & os.ModeDevice) == 0 {
|
|
| 485 |
+ return fmt.Errorf("%q should be a device, but is not", resource.Path())
|
|
| 486 |
+ } else {
|
|
| 487 |
+ major, minor, err := devices.DeviceInfo(fi) |
|
| 488 |
+ if err != nil {
|
|
| 489 |
+ return err |
|
| 490 |
+ } |
|
| 491 |
+ if major != r.Major() || minor != r.Minor() {
|
|
| 492 |
+ if err := c.driver.Remove(fp); err != nil {
|
|
| 493 |
+ return err |
|
| 494 |
+ } |
|
| 495 |
+ |
|
| 496 |
+ if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
|
| 497 |
+ return err |
|
| 498 |
+ } |
|
| 499 |
+ } |
|
| 500 |
+ } |
|
| 501 |
+ |
|
| 502 |
+ case NamedPipe: |
|
| 503 |
+ if fi == nil {
|
|
| 504 |
+ if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
|
|
| 505 |
+ return err |
|
| 506 |
+ } |
|
| 507 |
+ } else if (fi.Mode() & os.ModeNamedPipe) == 0 {
|
|
| 508 |
+ return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
|
|
| 509 |
+ } |
|
| 510 |
+ } |
|
| 511 |
+ |
|
| 512 |
+ if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
|
| 513 |
+ for _, path := range h.Paths() {
|
|
| 514 |
+ if path == resource.Path() {
|
|
| 515 |
+ continue |
|
| 516 |
+ } |
|
| 517 |
+ |
|
| 518 |
+ lp, err := c.fullpath(path) |
|
| 519 |
+ if err != nil {
|
|
| 520 |
+ return err |
|
| 521 |
+ } |
|
| 522 |
+ |
|
| 523 |
+ if _, fi := c.driver.Lstat(lp); fi == nil {
|
|
| 524 |
+ c.driver.Remove(lp) |
|
| 525 |
+ } |
|
| 526 |
+ if err := c.driver.Link(fp, lp); err != nil {
|
|
| 527 |
+ return err |
|
| 528 |
+ } |
|
| 529 |
+ } |
|
| 530 |
+ } |
|
| 531 |
+ |
|
| 532 |
+ // Update filemode if file was not created |
|
| 533 |
+ if chmod {
|
|
| 534 |
+ if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
|
|
| 535 |
+ return err |
|
| 536 |
+ } |
|
| 537 |
+ } |
|
| 538 |
+ |
|
| 539 |
+ if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
|
|
| 540 |
+ return err |
|
| 541 |
+ } |
|
| 542 |
+ |
|
| 543 |
+ if xattrer, ok := resource.(XAttrer); ok {
|
|
| 544 |
+ // For xattrs, only ensure that we have those defined in the resource |
|
| 545 |
+ // and their values are set. We can ignore other xattrs. In other words, |
|
| 546 |
+ // we only set xattres defined by resource but never remove. |
|
| 547 |
+ |
|
| 548 |
+ if _, ok := resource.(SymLink); ok {
|
|
| 549 |
+ lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver) |
|
| 550 |
+ if !ok {
|
|
| 551 |
+ return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
|
|
| 552 |
+ } |
|
| 553 |
+ if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
|
|
| 554 |
+ return err |
|
| 555 |
+ } |
|
| 556 |
+ } else {
|
|
| 557 |
+ xattrDriver, ok := c.driver.(driverpkg.XAttrDriver) |
|
| 558 |
+ if !ok {
|
|
| 559 |
+ return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
|
|
| 560 |
+ } |
|
| 561 |
+ if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
|
|
| 562 |
+ return err |
|
| 563 |
+ } |
|
| 564 |
+ } |
|
| 565 |
+ } |
|
| 566 |
+ |
|
| 567 |
+ return nil |
|
| 568 |
+} |
|
| 569 |
+ |
|
| 570 |
+// Walk provides a convenience function to call filepath.Walk correctly for |
|
| 571 |
+// the context. Otherwise identical to filepath.Walk, the path argument is |
|
| 572 |
+// corrected to be contained within the context. |
|
| 573 |
+func (c *context) Walk(fn filepath.WalkFunc) error {
|
|
| 574 |
+ root := c.root |
|
| 575 |
+ fi, err := c.driver.Lstat(c.root) |
|
| 576 |
+ if err == nil && fi.Mode()&os.ModeSymlink != 0 {
|
|
| 577 |
+ root, err = c.driver.Readlink(c.root) |
|
| 578 |
+ if err != nil {
|
|
| 579 |
+ return err |
|
| 580 |
+ } |
|
| 581 |
+ } |
|
| 582 |
+ return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
|
|
| 583 |
+ contained, err := c.containWithRoot(p, root) |
|
| 584 |
+ return fn(contained, fi, err) |
|
| 585 |
+ }) |
|
| 586 |
+} |
|
| 587 |
+ |
|
| 588 |
+// fullpath returns the system path for the resource, joined with the context |
|
| 589 |
+// root. The path p must be a part of the context. |
|
| 590 |
+func (c *context) fullpath(p string) (string, error) {
|
|
| 591 |
+ p = c.pathDriver.Join(c.root, p) |
|
| 592 |
+ if !strings.HasPrefix(p, c.root) {
|
|
| 593 |
+ return "", fmt.Errorf("invalid context path")
|
|
| 594 |
+ } |
|
| 595 |
+ |
|
| 596 |
+ return p, nil |
|
| 597 |
+} |
|
| 598 |
+ |
|
| 599 |
+// contain cleans and santizes the filesystem path p to be an absolute path, |
|
| 600 |
+// effectively relative to the context root. |
|
| 601 |
+func (c *context) contain(p string) (string, error) {
|
|
| 602 |
+ return c.containWithRoot(p, c.root) |
|
| 603 |
+} |
|
| 604 |
+ |
|
| 605 |
+// containWithRoot cleans and santizes the filesystem path p to be an absolute path, |
|
| 606 |
+// effectively relative to the passed root. Extra care should be used when calling this |
|
| 607 |
+// instead of contain. This is needed for Walk, as if context root is a symlink, |
|
| 608 |
+// it must be evaluated prior to the Walk |
|
| 609 |
+func (c *context) containWithRoot(p string, root string) (string, error) {
|
|
| 610 |
+ sanitized, err := c.pathDriver.Rel(root, p) |
|
| 611 |
+ if err != nil {
|
|
| 612 |
+ return "", err |
|
| 613 |
+ } |
|
| 614 |
+ |
|
| 615 |
+ // ZOMBIES(stevvooe): In certain cases, we may want to remap these to a |
|
| 616 |
+ // "containment error", so the caller can decide what to do. |
|
| 617 |
+ return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
|
|
| 618 |
+} |
|
| 619 |
+ |
|
| 620 |
+// digest returns the digest of the file at path p, relative to the root. |
|
| 621 |
+func (c *context) digest(p string) (digest.Digest, error) {
|
|
| 622 |
+ f, err := c.driver.Open(c.pathDriver.Join(c.root, p)) |
|
| 623 |
+ if err != nil {
|
|
| 624 |
+ return "", err |
|
| 625 |
+ } |
|
| 626 |
+ defer f.Close() |
|
| 627 |
+ |
|
| 628 |
+ return c.digester.Digest(f) |
|
| 629 |
+} |
|
| 630 |
+ |
|
| 631 |
+// resolveXAttrs attempts to resolve the extended attributes for the resource |
|
| 632 |
+// at the path fp, which is the full path to the resource. If the resource |
|
| 633 |
+// cannot have xattrs, nil will be returned. |
|
| 634 |
+func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
|
|
| 635 |
+ if fi.Mode().IsRegular() || fi.Mode().IsDir() {
|
|
| 636 |
+ xattrDriver, ok := c.driver.(driverpkg.XAttrDriver) |
|
| 637 |
+ if !ok {
|
|
| 638 |
+ log.Println("xattr extraction not supported")
|
|
| 639 |
+ return nil, ErrNotSupported |
|
| 640 |
+ } |
|
| 641 |
+ |
|
| 642 |
+ return xattrDriver.Getxattr(fp) |
|
| 643 |
+ } |
|
| 644 |
+ |
|
| 645 |
+ if fi.Mode()&os.ModeSymlink != 0 {
|
|
| 646 |
+ lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver) |
|
| 647 |
+ if !ok {
|
|
| 648 |
+ log.Println("xattr extraction for symlinks not supported")
|
|
| 649 |
+ return nil, ErrNotSupported |
|
| 650 |
+ } |
|
| 651 |
+ |
|
| 652 |
+ return lxattrDriver.LGetxattr(fp) |
|
| 653 |
+ } |
|
| 654 |
+ |
|
| 655 |
+ return nil, nil |
|
| 656 |
+} |
| 0 | 657 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,88 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "sort" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/opencontainers/go-digest" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// Digester produces a digest for a given read stream |
|
| 11 |
+type Digester interface {
|
|
| 12 |
+ Digest(io.Reader) (digest.Digest, error) |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// ContentProvider produces a read stream for a given digest |
|
| 16 |
+type ContentProvider interface {
|
|
| 17 |
+ Reader(digest.Digest) (io.ReadCloser, error) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+type simpleDigester struct {
|
|
| 21 |
+ algorithm digest.Algorithm |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
|
|
| 25 |
+ digester := sd.algorithm.Digester() |
|
| 26 |
+ |
|
| 27 |
+ if _, err := io.Copy(digester.Hash(), r); err != nil {
|
|
| 28 |
+ return "", err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ return digester.Digest(), nil |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the |
|
| 35 |
+// digests are not repeated and no two digests with the same algorithm have |
|
| 36 |
+// different values. Because a stable sort is used, this has the effect of |
|
| 37 |
+// "zipping" digest collections from multiple resources. |
|
| 38 |
+func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
|
|
| 39 |
+ sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here. |
|
| 40 |
+ seen := map[digest.Digest]struct{}{}
|
|
| 41 |
+ algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
|
|
| 42 |
+ |
|
| 43 |
+ var out []digest.Digest |
|
| 44 |
+ // uniqify the digests |
|
| 45 |
+ for _, d := range digests {
|
|
| 46 |
+ if _, ok := seen[d]; ok {
|
|
| 47 |
+ continue |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ seen[d] = struct{}{}
|
|
| 51 |
+ algs[d.Algorithm()] = append(algs[d.Algorithm()], d) |
|
| 52 |
+ |
|
| 53 |
+ if len(algs[d.Algorithm()]) > 1 {
|
|
| 54 |
+ return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
|
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ out = append(out, d) |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ return out, nil |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// digestsMatch compares the two sets of digests to see if they match. |
|
| 64 |
+func digestsMatch(as, bs []digest.Digest) bool {
|
|
| 65 |
+ all := append(as, bs...) |
|
| 66 |
+ |
|
| 67 |
+ uniqified, err := uniqifyDigests(all...) |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ // the only error uniqifyDigests returns is when the digests disagree. |
|
| 70 |
+ return false |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ disjoint := len(as) + len(bs) |
|
| 74 |
+ if len(uniqified) == disjoint {
|
|
| 75 |
+ // if these two sets have the same cardinality, we know both sides |
|
| 76 |
+ // didn't share any digests. |
|
| 77 |
+ return false |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ return true |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+type digestSlice []digest.Digest |
|
| 84 |
+ |
|
| 85 |
+func (p digestSlice) Len() int { return len(p) }
|
|
| 86 |
+func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
|
|
| 87 |
+func (p digestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
| 0 | 88 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,113 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ "strconv" |
|
| 8 |
+ "strings" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// TODO(stevvooe): This needs a lot of work before we can call it useful. |
|
| 12 |
+ |
|
| 13 |
+type groupIndex struct {
|
|
| 14 |
+ byName map[string]*group |
|
| 15 |
+ byGID map[int]*group |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func getGroupIndex() (*groupIndex, error) {
|
|
| 19 |
+ f, err := os.Open("/etc/group")
|
|
| 20 |
+ if err != nil {
|
|
| 21 |
+ return nil, err |
|
| 22 |
+ } |
|
| 23 |
+ defer f.Close() |
|
| 24 |
+ |
|
| 25 |
+ groups, err := parseGroups(f) |
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ return nil, err |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ return newGroupIndex(groups), nil |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func newGroupIndex(groups []group) *groupIndex {
|
|
| 34 |
+ gi := &groupIndex{
|
|
| 35 |
+ byName: make(map[string]*group), |
|
| 36 |
+ byGID: make(map[int]*group), |
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ for i, group := range groups {
|
|
| 40 |
+ gi.byGID[group.gid] = &groups[i] |
|
| 41 |
+ gi.byName[group.name] = &groups[i] |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ return gi |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+type group struct {
|
|
| 48 |
+ name string |
|
| 49 |
+ gid int |
|
| 50 |
+ members []string |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+func getGroupName(gid int) (string, error) {
|
|
| 54 |
+ f, err := os.Open("/etc/group")
|
|
| 55 |
+ if err != nil {
|
|
| 56 |
+ return "", err |
|
| 57 |
+ } |
|
| 58 |
+ defer f.Close() |
|
| 59 |
+ |
|
| 60 |
+ groups, err := parseGroups(f) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return "", err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ for _, group := range groups {
|
|
| 66 |
+ if group.gid == gid {
|
|
| 67 |
+ return group.name, nil |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ return "", fmt.Errorf("no group for gid")
|
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// parseGroups parses an /etc/group file for group names, ids and membership. |
|
| 75 |
+// This is unix specific. |
|
| 76 |
+func parseGroups(rd io.Reader) ([]group, error) {
|
|
| 77 |
+ var groups []group |
|
| 78 |
+ scanner := bufio.NewScanner(rd) |
|
| 79 |
+ |
|
| 80 |
+ for scanner.Scan() {
|
|
| 81 |
+ if strings.HasPrefix(scanner.Text(), "#") {
|
|
| 82 |
+ continue // skip comment |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ parts := strings.SplitN(scanner.Text(), ":", 4) |
|
| 86 |
+ |
|
| 87 |
+ if len(parts) != 4 {
|
|
| 88 |
+ return nil, fmt.Errorf("bad entry: %q", scanner.Text())
|
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ name, _, sgid, smembers := parts[0], parts[1], parts[2], parts[3] |
|
| 92 |
+ |
|
| 93 |
+ gid, err := strconv.Atoi(sgid) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return nil, fmt.Errorf("bad gid: %q", gid)
|
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ members := strings.Split(smembers, ",") |
|
| 99 |
+ |
|
| 100 |
+ groups = append(groups, group{
|
|
| 101 |
+ name: name, |
|
| 102 |
+ gid: gid, |
|
| 103 |
+ members: members, |
|
| 104 |
+ }) |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ if scanner.Err() != nil {
|
|
| 108 |
+ return nil, scanner.Err() |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ return groups, nil |
|
| 112 |
+} |
| 0 | 113 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+var ( |
|
| 8 |
+ errNotAHardLink = fmt.Errorf("invalid hardlink")
|
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+type hardlinkManager struct {
|
|
| 12 |
+ hardlinks map[hardlinkKey][]Resource |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func newHardlinkManager() *hardlinkManager {
|
|
| 16 |
+ return &hardlinkManager{
|
|
| 17 |
+ hardlinks: map[hardlinkKey][]Resource{},
|
|
| 18 |
+ } |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// Add attempts to add the resource to the hardlink manager. If the resource |
|
| 22 |
+// cannot be considered as a hardlink candidate, errNotAHardLink is returned. |
|
| 23 |
+func (hlm *hardlinkManager) Add(fi os.FileInfo, resource Resource) error {
|
|
| 24 |
+ if _, ok := resource.(Hardlinkable); !ok {
|
|
| 25 |
+ return errNotAHardLink |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ key, err := newHardlinkKey(fi) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return err |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ hlm.hardlinks[key] = append(hlm.hardlinks[key], resource) |
|
| 34 |
+ |
|
| 35 |
+ return nil |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// Merge processes the current state of the hardlink manager and merges any |
|
| 39 |
+// shared nodes into hardlinked resources. |
|
| 40 |
+func (hlm *hardlinkManager) Merge() ([]Resource, error) {
|
|
| 41 |
+ var resources []Resource |
|
| 42 |
+ for key, linked := range hlm.hardlinks {
|
|
| 43 |
+ if len(linked) < 1 {
|
|
| 44 |
+ return nil, fmt.Errorf("no hardlink entrys for dev, inode pair: %#v", key)
|
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ merged, err := Merge(linked...) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return nil, fmt.Errorf("error merging hardlink: %v", err)
|
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ resources = append(resources, merged) |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ return resources, nil |
|
| 56 |
+} |
| 0 | 57 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+// +build linux darwin freebsd solaris |
|
| 1 |
+ |
|
| 2 |
+package continuity |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "os" |
|
| 7 |
+ "syscall" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// hardlinkKey provides a tuple-key for managing hardlinks. This is system- |
|
| 11 |
+// specific. |
|
| 12 |
+type hardlinkKey struct {
|
|
| 13 |
+ dev uint64 |
|
| 14 |
+ inode uint64 |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// newHardlinkKey returns a hardlink key for the provided file info. If the |
|
| 18 |
+// resource does not represent a possible hardlink, errNotAHardLink will be |
|
| 19 |
+// returned. |
|
| 20 |
+func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
|
|
| 21 |
+ sys, ok := fi.Sys().(*syscall.Stat_t) |
|
| 22 |
+ if !ok {
|
|
| 23 |
+ return hardlinkKey{}, fmt.Errorf("cannot resolve (*syscall.Stat_t) from os.FileInfo")
|
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ if sys.Nlink < 2 {
|
|
| 27 |
+ // NOTE(stevvooe): This is not always true for all filesystems. We |
|
| 28 |
+ // should somehow detect this and provided a slow "polyfill" that |
|
| 29 |
+ // leverages os.SameFile if we detect a filesystem where link counts |
|
| 30 |
+ // is not really supported. |
|
| 31 |
+ return hardlinkKey{}, errNotAHardLink
|
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ return hardlinkKey{dev: uint64(sys.Dev), inode: uint64(sys.Ino)}, nil
|
|
| 35 |
+} |
| 0 | 36 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import "os" |
|
| 3 |
+ |
|
| 4 |
+type hardlinkKey struct{}
|
|
| 5 |
+ |
|
| 6 |
+func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
|
|
| 7 |
+ // NOTE(stevvooe): Obviously, this is not yet implemented. However, the |
|
| 8 |
+ // makings of an implementation are available in src/os/types_windows.go. More |
|
| 9 |
+ // investigation needs to be done to figure out exactly how to do this. |
|
| 10 |
+ return hardlinkKey{}, errNotAHardLink
|
|
| 11 |
+} |
| 0 | 12 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,47 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "io" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// AtomicWriteFile atomically writes data to a file by first writing to a |
|
| 11 |
+// temp file and calling rename. |
|
| 12 |
+func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
|
| 13 |
+ buf := bytes.NewBuffer(data) |
|
| 14 |
+ return atomicWriteFile(filename, buf, int64(len(data)), perm) |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// atomicWriteFile writes data to a file by first writing to a temp |
|
| 18 |
+// file and calling rename. |
|
| 19 |
+func atomicWriteFile(filename string, r io.Reader, dataSize int64, perm os.FileMode) error {
|
|
| 20 |
+ f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename)) |
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return err |
|
| 23 |
+ } |
|
| 24 |
+ err = os.Chmod(f.Name(), perm) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ f.Close() |
|
| 27 |
+ return err |
|
| 28 |
+ } |
|
| 29 |
+ n, err := io.Copy(f, r) |
|
| 30 |
+ if err == nil && n < dataSize {
|
|
| 31 |
+ f.Close() |
|
| 32 |
+ return io.ErrShortWrite |
|
| 33 |
+ } |
|
| 34 |
+ if err != nil {
|
|
| 35 |
+ f.Close() |
|
| 36 |
+ return err |
|
| 37 |
+ } |
|
| 38 |
+ if err := f.Sync(); err != nil {
|
|
| 39 |
+ f.Close() |
|
| 40 |
+ return err |
|
| 41 |
+ } |
|
| 42 |
+ if err := f.Close(); err != nil {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ return os.Rename(f.Name(), filename) |
|
| 46 |
+} |
| 0 | 47 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,144 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "log" |
|
| 6 |
+ "os" |
|
| 7 |
+ "sort" |
|
| 8 |
+ |
|
| 9 |
+ pb "github.com/containerd/continuity/proto" |
|
| 10 |
+ "github.com/golang/protobuf/proto" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// Manifest provides the contents of a manifest. Users of this struct should |
|
| 14 |
+// not typically modify any fields directly. |
|
| 15 |
+type Manifest struct {
|
|
| 16 |
+ // Resources specifies all the resources for a manifest in order by path. |
|
| 17 |
+ Resources []Resource |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func Unmarshal(p []byte) (*Manifest, error) {
|
|
| 21 |
+ var bm pb.Manifest |
|
| 22 |
+ |
|
| 23 |
+ if err := proto.Unmarshal(p, &bm); err != nil {
|
|
| 24 |
+ return nil, err |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ var m Manifest |
|
| 28 |
+ for _, b := range bm.Resource {
|
|
| 29 |
+ r, err := fromProto(b) |
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ return nil, err |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ m.Resources = append(m.Resources, r) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ return &m, nil |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func Marshal(m *Manifest) ([]byte, error) {
|
|
| 41 |
+ var bm pb.Manifest |
|
| 42 |
+ for _, resource := range m.Resources {
|
|
| 43 |
+ bm.Resource = append(bm.Resource, toProto(resource)) |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ return proto.Marshal(&bm) |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func MarshalText(w io.Writer, m *Manifest) error {
|
|
| 50 |
+ var bm pb.Manifest |
|
| 51 |
+ for _, resource := range m.Resources {
|
|
| 52 |
+ bm.Resource = append(bm.Resource, toProto(resource)) |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ return proto.MarshalText(w, &bm) |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// BuildManifest creates the manifest for the given context |
|
| 59 |
+func BuildManifest(ctx Context) (*Manifest, error) {
|
|
| 60 |
+ resourcesByPath := map[string]Resource{}
|
|
| 61 |
+ hardlinks := newHardlinkManager() |
|
| 62 |
+ |
|
| 63 |
+ if err := ctx.Walk(func(p string, fi os.FileInfo, err error) error {
|
|
| 64 |
+ if err != nil {
|
|
| 65 |
+ return fmt.Errorf("error walking %s: %v", p, err)
|
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ if p == string(os.PathSeparator) {
|
|
| 69 |
+ // skip root |
|
| 70 |
+ return nil |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ resource, err := ctx.Resource(p, fi) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ if err == ErrNotFound {
|
|
| 76 |
+ return nil |
|
| 77 |
+ } |
|
| 78 |
+ log.Printf("error getting resource %q: %v", p, err)
|
|
| 79 |
+ return err |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ // add to the hardlink manager |
|
| 83 |
+ if err := hardlinks.Add(fi, resource); err == nil {
|
|
| 84 |
+ // Resource has been accepted by hardlink manager so we don't add |
|
| 85 |
+ // it to the resourcesByPath until we merge at the end. |
|
| 86 |
+ return nil |
|
| 87 |
+ } else if err != errNotAHardLink {
|
|
| 88 |
+ // handle any other case where we have a proper error. |
|
| 89 |
+ return fmt.Errorf("adding hardlink %s: %v", p, err)
|
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ resourcesByPath[p] = resource |
|
| 93 |
+ |
|
| 94 |
+ return nil |
|
| 95 |
+ }); err != nil {
|
|
| 96 |
+ return nil, err |
|
| 97 |
+ } |
|
| 98 |
+ |
|
| 99 |
+ // merge and post-process the hardlinks. |
|
| 100 |
+ hardlinked, err := hardlinks.Merge() |
|
| 101 |
+ if err != nil {
|
|
| 102 |
+ return nil, err |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ for _, resource := range hardlinked {
|
|
| 106 |
+ resourcesByPath[resource.Path()] = resource |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ var resources []Resource |
|
| 110 |
+ for _, resource := range resourcesByPath {
|
|
| 111 |
+ resources = append(resources, resource) |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ sort.Stable(ByPath(resources)) |
|
| 115 |
+ |
|
| 116 |
+ return &Manifest{
|
|
| 117 |
+ Resources: resources, |
|
| 118 |
+ }, nil |
|
| 119 |
+} |
|
| 120 |
+ |
|
| 121 |
+// VerifyManifest verifies all the resources in a manifest |
|
| 122 |
+// against files from the given context. |
|
| 123 |
+func VerifyManifest(ctx Context, manifest *Manifest) error {
|
|
| 124 |
+ for _, resource := range manifest.Resources {
|
|
| 125 |
+ if err := ctx.Verify(resource); err != nil {
|
|
| 126 |
+ return err |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 129 |
+ |
|
| 130 |
+ return nil |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+// ApplyManifest applies on the resources in a manifest to |
|
| 134 |
+// the given context. |
|
| 135 |
+func ApplyManifest(ctx Context, manifest *Manifest) error {
|
|
| 136 |
+ for _, resource := range manifest.Resources {
|
|
| 137 |
+ if err := ctx.Apply(resource); err != nil {
|
|
| 138 |
+ return err |
|
| 139 |
+ } |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ return nil |
|
| 143 |
+} |
| 0 | 3 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,181 @@ |
| 0 |
+// Code generated by protoc-gen-go. |
|
| 1 |
+// source: manifest.proto |
|
| 2 |
+// DO NOT EDIT! |
|
| 3 |
+ |
|
| 4 |
+/* |
|
| 5 |
+Package proto is a generated protocol buffer package. |
|
| 6 |
+ |
|
| 7 |
+It is generated from these files: |
|
| 8 |
+ manifest.proto |
|
| 9 |
+ |
|
| 10 |
+It has these top-level messages: |
|
| 11 |
+ Manifest |
|
| 12 |
+ Resource |
|
| 13 |
+ XAttr |
|
| 14 |
+ ADSEntry |
|
| 15 |
+*/ |
|
| 16 |
+package proto |
|
| 17 |
+ |
|
| 18 |
+import proto1 "github.com/golang/protobuf/proto" |
|
| 19 |
+import fmt "fmt" |
|
| 20 |
+import math "math" |
|
| 21 |
+ |
|
| 22 |
+// Reference imports to suppress errors if they are not otherwise used. |
|
| 23 |
+var _ = proto1.Marshal |
|
| 24 |
+var _ = fmt.Errorf |
|
| 25 |
+var _ = math.Inf |
|
| 26 |
+ |
|
| 27 |
+// This is a compile-time assertion to ensure that this generated file |
|
| 28 |
+// is compatible with the proto package it is being compiled against. |
|
| 29 |
+// A compilation error at this line likely means your copy of the |
|
| 30 |
+// proto package needs to be updated. |
|
| 31 |
+const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package |
|
| 32 |
+ |
|
| 33 |
+// Manifest specifies the entries in a container bundle, keyed and sorted by |
|
| 34 |
+// path. |
|
| 35 |
+type Manifest struct {
|
|
| 36 |
+ Resource []*Resource `protobuf:"bytes,1,rep,name=resource" json:"resource,omitempty"` |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (m *Manifest) Reset() { *m = Manifest{} }
|
|
| 40 |
+func (m *Manifest) String() string { return proto1.CompactTextString(m) }
|
|
| 41 |
+func (*Manifest) ProtoMessage() {}
|
|
| 42 |
+func (*Manifest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
|
| 43 |
+ |
|
| 44 |
+func (m *Manifest) GetResource() []*Resource {
|
|
| 45 |
+ if m != nil {
|
|
| 46 |
+ return m.Resource |
|
| 47 |
+ } |
|
| 48 |
+ return nil |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+type Resource struct {
|
|
| 52 |
+ // Path specifies the path from the bundle root. If more than one |
|
| 53 |
+ // path is present, the entry may represent a hardlink, rather than using |
|
| 54 |
+ // a link target. The path format is operating system specific. |
|
| 55 |
+ Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"` |
|
| 56 |
+ // Uid specifies the user id for the resource. |
|
| 57 |
+ Uid int64 `protobuf:"varint,2,opt,name=uid" json:"uid,omitempty"` |
|
| 58 |
+ // Gid specifies the group id for the resource. |
|
| 59 |
+ Gid int64 `protobuf:"varint,3,opt,name=gid" json:"gid,omitempty"` |
|
| 60 |
+ // user and group are not currently used but their field numbers have been |
|
| 61 |
+ // reserved for future use. As such, they are marked as deprecated. |
|
| 62 |
+ User string `protobuf:"bytes,4,opt,name=user" json:"user,omitempty"` |
|
| 63 |
+ Group string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"` |
|
| 64 |
+ // Mode defines the file mode and permissions. We've used the same |
|
| 65 |
+ // bit-packing from Go's os package, |
|
| 66 |
+ // http://golang.org/pkg/os/#FileMode, since they've done the work of |
|
| 67 |
+ // creating a cross-platform layout. |
|
| 68 |
+ Mode uint32 `protobuf:"varint,6,opt,name=mode" json:"mode,omitempty"` |
|
| 69 |
+ // Size specifies the size in bytes of the resource. This is only valid |
|
| 70 |
+ // for regular files. |
|
| 71 |
+ Size uint64 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"` |
|
| 72 |
+ // Digest specifies the content digest of the target file. Only valid for |
|
| 73 |
+ // regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>. |
|
| 74 |
+ // For detailed information about the format, please refer to OCI Image Spec: |
|
| 75 |
+ // https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification |
|
| 76 |
+ // The digests are sorted in lexical order and implementations may choose |
|
| 77 |
+ // which algorithms they prefer. |
|
| 78 |
+ Digest []string `protobuf:"bytes,8,rep,name=digest" json:"digest,omitempty"` |
|
| 79 |
+ // Target defines the target of a hard or soft link. Absolute links start |
|
| 80 |
+ // with a slash and specify the resource relative to the bundle root. |
|
| 81 |
+ // Relative links do not start with a slash and are relative to the |
|
| 82 |
+ // resource path. |
|
| 83 |
+ Target string `protobuf:"bytes,9,opt,name=target" json:"target,omitempty"` |
|
| 84 |
+ // Major specifies the major device number for character and block devices. |
|
| 85 |
+ Major uint64 `protobuf:"varint,10,opt,name=major" json:"major,omitempty"` |
|
| 86 |
+ // Minor specifies the minor device number for character and block devices. |
|
| 87 |
+ Minor uint64 `protobuf:"varint,11,opt,name=minor" json:"minor,omitempty"` |
|
| 88 |
+ // Xattr provides storage for extended attributes for the target resource. |
|
| 89 |
+ Xattr []*XAttr `protobuf:"bytes,12,rep,name=xattr" json:"xattr,omitempty"` |
|
| 90 |
+ // Ads stores one or more alternate data streams for the target resource. |
|
| 91 |
+ Ads []*ADSEntry `protobuf:"bytes,13,rep,name=ads" json:"ads,omitempty"` |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (m *Resource) Reset() { *m = Resource{} }
|
|
| 95 |
+func (m *Resource) String() string { return proto1.CompactTextString(m) }
|
|
| 96 |
+func (*Resource) ProtoMessage() {}
|
|
| 97 |
+func (*Resource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
|
| 98 |
+ |
|
| 99 |
+func (m *Resource) GetXattr() []*XAttr {
|
|
| 100 |
+ if m != nil {
|
|
| 101 |
+ return m.Xattr |
|
| 102 |
+ } |
|
| 103 |
+ return nil |
|
| 104 |
+} |
|
| 105 |
+ |
|
| 106 |
+func (m *Resource) GetAds() []*ADSEntry {
|
|
| 107 |
+ if m != nil {
|
|
| 108 |
+ return m.Ads |
|
| 109 |
+ } |
|
| 110 |
+ return nil |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+// XAttr encodes extended attributes for a resource. |
|
| 114 |
+type XAttr struct {
|
|
| 115 |
+ // Name specifies the attribute name. |
|
| 116 |
+ Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` |
|
| 117 |
+ // Data specifies the associated data for the attribute. |
|
| 118 |
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` |
|
| 119 |
+} |
|
| 120 |
+ |
|
| 121 |
+func (m *XAttr) Reset() { *m = XAttr{} }
|
|
| 122 |
+func (m *XAttr) String() string { return proto1.CompactTextString(m) }
|
|
| 123 |
+func (*XAttr) ProtoMessage() {}
|
|
| 124 |
+func (*XAttr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
|
| 125 |
+ |
|
| 126 |
+// ADSEntry encodes information for a Windows Alternate Data Stream. |
|
| 127 |
+type ADSEntry struct {
|
|
| 128 |
+ // Name specifices the stream name. |
|
| 129 |
+ Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` |
|
| 130 |
+ // Data specifies the stream data. |
|
| 131 |
+ // See also the description about the digest below. |
|
| 132 |
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` |
|
| 133 |
+ // Digest is a CAS representation of the stream data. |
|
| 134 |
+ // |
|
| 135 |
+ // At least one of data or digest MUST be specified, and either one of them |
|
| 136 |
+ // SHOULD be specified. |
|
| 137 |
+ // |
|
| 138 |
+ // How to access the actual data using the digest is implementation-specific, |
|
| 139 |
+ // and implementations can choose not to implement digest. |
|
| 140 |
+ // So, digest SHOULD be used only when the stream data is large. |
|
| 141 |
+ Digest string `protobuf:"bytes,3,opt,name=digest" json:"digest,omitempty"` |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func (m *ADSEntry) Reset() { *m = ADSEntry{} }
|
|
| 145 |
+func (m *ADSEntry) String() string { return proto1.CompactTextString(m) }
|
|
| 146 |
+func (*ADSEntry) ProtoMessage() {}
|
|
| 147 |
+func (*ADSEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
|
| 148 |
+ |
|
| 149 |
+func init() {
|
|
| 150 |
+ proto1.RegisterType((*Manifest)(nil), "proto.Manifest") |
|
| 151 |
+ proto1.RegisterType((*Resource)(nil), "proto.Resource") |
|
| 152 |
+ proto1.RegisterType((*XAttr)(nil), "proto.XAttr") |
|
| 153 |
+ proto1.RegisterType((*ADSEntry)(nil), "proto.ADSEntry") |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+func init() { proto1.RegisterFile("manifest.proto", fileDescriptor0) }
|
|
| 157 |
+ |
|
| 158 |
+var fileDescriptor0 = []byte{
|
|
| 159 |
+ // 317 bytes of a gzipped FileDescriptorProto |
|
| 160 |
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0x4f, 0x4b, 0xf3, 0x40, |
|
| 161 |
+ 0x10, 0xc6, 0x49, 0x93, 0xf4, 0x4d, 0xa7, 0xed, 0xab, 0x2c, 0x52, 0xe6, 0x18, 0x73, 0x0a, 0x08, |
|
| 162 |
+ 0x15, 0xf4, 0xe0, 0xb9, 0xa2, 0x17, 0xc1, 0xcb, 0x7a, 0xf1, 0xba, 0xba, 0x6b, 0x5c, 0x21, 0xd9, |
|
| 163 |
+ 0xb0, 0xd9, 0x80, 0xfa, 0xe5, 0xfc, 0x6a, 0x32, 0xb3, 0x69, 0xd1, 0x9b, 0xa7, 0x3c, 0xcf, 0x6f, |
|
| 164 |
+ 0xfe, 0x64, 0xf6, 0x81, 0xff, 0xad, 0xea, 0xec, 0x8b, 0x19, 0xc2, 0xb6, 0xf7, 0x2e, 0x38, 0x91, |
|
| 165 |
+ 0xf3, 0xa7, 0xba, 0x82, 0xe2, 0x7e, 0x2a, 0x88, 0x33, 0x28, 0xbc, 0x19, 0xdc, 0xe8, 0x9f, 0x0d, |
|
| 166 |
+ 0x26, 0x65, 0x5a, 0x2f, 0x2f, 0x8e, 0x62, 0xf3, 0x56, 0x4e, 0x58, 0x1e, 0x1a, 0xaa, 0xaf, 0x19, |
|
| 167 |
+ 0x14, 0x7b, 0x2c, 0x04, 0x64, 0xbd, 0x0a, 0xaf, 0x3c, 0xb5, 0x90, 0xac, 0xc5, 0x31, 0xa4, 0xa3, |
|
| 168 |
+ 0xd5, 0x38, 0x2b, 0x93, 0x3a, 0x95, 0x24, 0x89, 0x34, 0x56, 0x63, 0x1a, 0x49, 0x63, 0xb5, 0xd8, |
|
| 169 |
+ 0x40, 0x36, 0x0e, 0xc6, 0x63, 0x56, 0x26, 0xf5, 0xe2, 0x7a, 0x86, 0x89, 0x64, 0x2f, 0x10, 0xf2, |
|
| 170 |
+ 0xc6, 0xbb, 0xb1, 0xc7, 0xfc, 0x50, 0x88, 0x80, 0xfe, 0xd4, 0x3a, 0x6d, 0x70, 0x5e, 0x26, 0xf5, |
|
| 171 |
+ 0x5a, 0xb2, 0x26, 0x36, 0xd8, 0x4f, 0x83, 0xff, 0xca, 0xa4, 0xce, 0x24, 0x6b, 0xb1, 0x81, 0xb9, |
|
| 172 |
+ 0xb6, 0x8d, 0x19, 0x02, 0x16, 0x7c, 0xd3, 0xe4, 0x88, 0x07, 0xe5, 0x1b, 0x13, 0x70, 0x41, 0xab, |
|
| 173 |
+ 0xe5, 0xe4, 0xc4, 0x09, 0xe4, 0xad, 0x7a, 0x73, 0x1e, 0x81, 0x97, 0x44, 0xc3, 0xd4, 0x76, 0xce, |
|
| 174 |
+ 0xe3, 0x72, 0xa2, 0x64, 0x44, 0x05, 0xf9, 0xbb, 0x0a, 0xc1, 0xe3, 0x8a, 0x43, 0x5a, 0x4d, 0x21, |
|
| 175 |
+ 0x3d, 0xee, 0x42, 0xf0, 0x32, 0x96, 0xc4, 0x29, 0xa4, 0x4a, 0x0f, 0xb8, 0xfe, 0x15, 0xe3, 0xee, |
|
| 176 |
+ 0xe6, 0xe1, 0xb6, 0x0b, 0xfe, 0x43, 0x52, 0xad, 0x3a, 0x87, 0x9c, 0x47, 0xe8, 0xfe, 0x4e, 0xb5, |
|
| 177 |
+ 0x94, 0x39, 0x5d, 0xc4, 0x9a, 0x98, 0x56, 0x41, 0x71, 0x7c, 0x2b, 0xc9, 0xba, 0xba, 0x83, 0x62, |
|
| 178 |
+ 0xbf, 0xe1, 0xaf, 0x33, 0x3f, 0x72, 0x48, 0xe3, 0x7b, 0xa3, 0x7b, 0x9a, 0xf3, 0x45, 0x97, 0xdf, |
|
| 179 |
+ 0x01, 0x00, 0x00, 0xff, 0xff, 0xef, 0x27, 0x99, 0xf7, 0x17, 0x02, 0x00, 0x00, |
|
| 180 |
+} |
| 0 | 181 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,97 @@ |
| 0 |
+syntax = "proto3"; |
|
| 1 |
+ |
|
| 2 |
+package proto; |
|
| 3 |
+ |
|
| 4 |
+// Manifest specifies the entries in a container bundle, keyed and sorted by |
|
| 5 |
+// path. |
|
| 6 |
+message Manifest {
|
|
| 7 |
+ repeated Resource resource = 1; |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+message Resource {
|
|
| 11 |
+ // Path specifies the path from the bundle root. If more than one |
|
| 12 |
+ // path is present, the entry may represent a hardlink, rather than using |
|
| 13 |
+ // a link target. The path format is operating system specific. |
|
| 14 |
+ repeated string path = 1; |
|
| 15 |
+ |
|
| 16 |
+ // NOTE(stevvooe): Need to define clear precedence for user/group/uid/gid precedence. |
|
| 17 |
+ |
|
| 18 |
+ // Uid specifies the user id for the resource. |
|
| 19 |
+ int64 uid = 2; |
|
| 20 |
+ |
|
| 21 |
+ // Gid specifies the group id for the resource. |
|
| 22 |
+ int64 gid = 3; |
|
| 23 |
+ |
|
| 24 |
+ // user and group are not currently used but their field numbers have been |
|
| 25 |
+ // reserved for future use. As such, they are marked as deprecated. |
|
| 26 |
+ string user = 4 [deprecated=true]; // "deprecated" stands for "reserved" here |
|
| 27 |
+ string group = 5 [deprecated=true]; // "deprecated" stands for "reserved" here |
|
| 28 |
+ |
|
| 29 |
+ // Mode defines the file mode and permissions. We've used the same |
|
| 30 |
+ // bit-packing from Go's os package, |
|
| 31 |
+ // http://golang.org/pkg/os/#FileMode, since they've done the work of |
|
| 32 |
+ // creating a cross-platform layout. |
|
| 33 |
+ uint32 mode = 6; |
|
| 34 |
+ |
|
| 35 |
+ // NOTE(stevvooe): Beyond here, we start defining type specific fields. |
|
| 36 |
+ |
|
| 37 |
+ // Size specifies the size in bytes of the resource. This is only valid |
|
| 38 |
+ // for regular files. |
|
| 39 |
+ uint64 size = 7; |
|
| 40 |
+ |
|
| 41 |
+ // Digest specifies the content digest of the target file. Only valid for |
|
| 42 |
+ // regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>. |
|
| 43 |
+ // For detailed information about the format, please refer to OCI Image Spec: |
|
| 44 |
+ // https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification |
|
| 45 |
+ // The digests are sorted in lexical order and implementations may choose |
|
| 46 |
+ // which algorithms they prefer. |
|
| 47 |
+ repeated string digest = 8; |
|
| 48 |
+ |
|
| 49 |
+ // Target defines the target of a hard or soft link. Absolute links start |
|
| 50 |
+ // with a slash and specify the resource relative to the bundle root. |
|
| 51 |
+ // Relative links do not start with a slash and are relative to the |
|
| 52 |
+ // resource path. |
|
| 53 |
+ string target = 9; |
|
| 54 |
+ |
|
| 55 |
+ // Major specifies the major device number for character and block devices. |
|
| 56 |
+ uint64 major = 10; |
|
| 57 |
+ |
|
| 58 |
+ // Minor specifies the minor device number for character and block devices. |
|
| 59 |
+ uint64 minor = 11; |
|
| 60 |
+ |
|
| 61 |
+ // Xattr provides storage for extended attributes for the target resource. |
|
| 62 |
+ repeated XAttr xattr = 12; |
|
| 63 |
+ |
|
| 64 |
+ // Ads stores one or more alternate data streams for the target resource. |
|
| 65 |
+ repeated ADSEntry ads = 13; |
|
| 66 |
+ |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+// XAttr encodes extended attributes for a resource. |
|
| 70 |
+message XAttr {
|
|
| 71 |
+ // Name specifies the attribute name. |
|
| 72 |
+ string name = 1; |
|
| 73 |
+ |
|
| 74 |
+ // Data specifies the associated data for the attribute. |
|
| 75 |
+ bytes data = 2; |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// ADSEntry encodes information for a Windows Alternate Data Stream. |
|
| 79 |
+message ADSEntry {
|
|
| 80 |
+ // Name specifices the stream name. |
|
| 81 |
+ string name = 1; |
|
| 82 |
+ |
|
| 83 |
+ // Data specifies the stream data. |
|
| 84 |
+ // See also the description about the digest below. |
|
| 85 |
+ bytes data = 2; |
|
| 86 |
+ |
|
| 87 |
+ // Digest is a CAS representation of the stream data. |
|
| 88 |
+ // |
|
| 89 |
+ // At least one of data or digest MUST be specified, and either one of them |
|
| 90 |
+ // SHOULD be specified. |
|
| 91 |
+ // |
|
| 92 |
+ // How to access the actual data using the digest is implementation-specific, |
|
| 93 |
+ // and implementations can choose not to implement digest. |
|
| 94 |
+ // So, digest SHOULD be used only when the stream data is large. |
|
| 95 |
+ string digest = 3; |
|
| 96 |
+} |
| 0 | 97 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,574 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "os" |
|
| 6 |
+ "reflect" |
|
| 7 |
+ "sort" |
|
| 8 |
+ |
|
| 9 |
+ pb "github.com/containerd/continuity/proto" |
|
| 10 |
+ "github.com/opencontainers/go-digest" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// TODO(stevvooe): A record based model, somewhat sketched out at the bottom |
|
| 14 |
+// of this file, will be more flexible. Another possibly is to tie the package |
|
| 15 |
+// interface directly to the protobuf type. This will have efficiency |
|
| 16 |
+// advantages at the cost coupling the nasty codegen types to the exported |
|
| 17 |
+// interface. |
|
| 18 |
+ |
|
| 19 |
+type Resource interface {
|
|
| 20 |
+ // Path provides the primary resource path relative to the bundle root. In |
|
| 21 |
+ // cases where resources have more than one path, such as with hard links, |
|
| 22 |
+ // this will return the primary path, which is often just the first entry. |
|
| 23 |
+ Path() string |
|
| 24 |
+ |
|
| 25 |
+ // Mode returns the |
|
| 26 |
+ Mode() os.FileMode |
|
| 27 |
+ |
|
| 28 |
+ UID() int64 |
|
| 29 |
+ GID() int64 |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// ByPath provides the canonical sort order for a set of resources. Use with |
|
| 33 |
+// sort.Stable for deterministic sorting. |
|
| 34 |
+type ByPath []Resource |
|
| 35 |
+ |
|
| 36 |
+func (bp ByPath) Len() int { return len(bp) }
|
|
| 37 |
+func (bp ByPath) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
|
|
| 38 |
+func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
|
|
| 39 |
+ |
|
| 40 |
+type XAttrer interface {
|
|
| 41 |
+ XAttrs() map[string][]byte |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+// Hardlinkable is an interface that a resource type satisfies if it can be a |
|
| 45 |
+// hardlink target. |
|
| 46 |
+type Hardlinkable interface {
|
|
| 47 |
+ // Paths returns all paths of the resource, including the primary path |
|
| 48 |
+ // returned by Resource.Path. If len(Paths()) > 1, the resource is a hard |
|
| 49 |
+ // link. |
|
| 50 |
+ Paths() []string |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+type RegularFile interface {
|
|
| 54 |
+ Resource |
|
| 55 |
+ XAttrer |
|
| 56 |
+ Hardlinkable |
|
| 57 |
+ |
|
| 58 |
+ Size() int64 |
|
| 59 |
+ Digests() []digest.Digest |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// Merge two or more Resources into new file. Typically, this should be |
|
| 63 |
+// used to merge regular files as hardlinks. If the files are not identical, |
|
| 64 |
+// other than Paths and Digests, the merge will fail and an error will be |
|
| 65 |
+// returned. |
|
| 66 |
+func Merge(fs ...Resource) (Resource, error) {
|
|
| 67 |
+ if len(fs) < 1 {
|
|
| 68 |
+ return nil, fmt.Errorf("please provide a resource to merge")
|
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if len(fs) == 1 {
|
|
| 72 |
+ return fs[0], nil |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ var paths []string |
|
| 76 |
+ var digests []digest.Digest |
|
| 77 |
+ bypath := map[string][]Resource{}
|
|
| 78 |
+ |
|
| 79 |
+ // The attributes are all compared against the first to make sure they |
|
| 80 |
+ // agree before adding to the above collections. If any of these don't |
|
| 81 |
+ // correctly validate, the merge fails. |
|
| 82 |
+ prototype := fs[0] |
|
| 83 |
+ xattrs := make(map[string][]byte) |
|
| 84 |
+ |
|
| 85 |
+ // initialize xattrs for use below. All files must have same xattrs. |
|
| 86 |
+ if prototypeXAttrer, ok := prototype.(XAttrer); ok {
|
|
| 87 |
+ for attr, value := range prototypeXAttrer.XAttrs() {
|
|
| 88 |
+ xattrs[attr] = value |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ for _, f := range fs {
|
|
| 93 |
+ h, isHardlinkable := f.(Hardlinkable) |
|
| 94 |
+ if !isHardlinkable {
|
|
| 95 |
+ return nil, errNotAHardLink |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ if f.Mode() != prototype.Mode() {
|
|
| 99 |
+ return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
|
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ if f.UID() != prototype.UID() {
|
|
| 103 |
+ return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
|
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ if f.GID() != prototype.GID() {
|
|
| 107 |
+ return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
|
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ if xattrer, ok := f.(XAttrer); ok {
|
|
| 111 |
+ fxattrs := xattrer.XAttrs() |
|
| 112 |
+ if !reflect.DeepEqual(fxattrs, xattrs) {
|
|
| 113 |
+ return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
|
|
| 114 |
+ } |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ for _, p := range h.Paths() {
|
|
| 118 |
+ pfs, ok := bypath[p] |
|
| 119 |
+ if !ok {
|
|
| 120 |
+ // ensure paths are unique by only appending on a new path. |
|
| 121 |
+ paths = append(paths, p) |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ bypath[p] = append(pfs, f) |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ if regFile, isRegFile := f.(RegularFile); isRegFile {
|
|
| 128 |
+ prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile) |
|
| 129 |
+ if !prototypeIsRegFile {
|
|
| 130 |
+ return nil, errors.New("prototype is not a regular file")
|
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ if regFile.Size() != prototypeRegFile.Size() {
|
|
| 134 |
+ return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
|
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ digests = append(digests, regFile.Digests()...) |
|
| 138 |
+ } else if device, isDevice := f.(Device); isDevice {
|
|
| 139 |
+ prototypeDevice, prototypeIsDevice := prototype.(Device) |
|
| 140 |
+ if !prototypeIsDevice {
|
|
| 141 |
+ return nil, errors.New("prototype is not a device")
|
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ if device.Major() != prototypeDevice.Major() {
|
|
| 145 |
+ return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
|
|
| 146 |
+ } |
|
| 147 |
+ if device.Minor() != prototypeDevice.Minor() {
|
|
| 148 |
+ return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
|
|
| 149 |
+ } |
|
| 150 |
+ } else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
|
|
| 151 |
+ _, prototypeIsNamedPipe := prototype.(NamedPipe) |
|
| 152 |
+ if !prototypeIsNamedPipe {
|
|
| 153 |
+ return nil, errors.New("prototype is not a named pipe")
|
|
| 154 |
+ } |
|
| 155 |
+ } else {
|
|
| 156 |
+ return nil, errNotAHardLink |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+ sort.Stable(sort.StringSlice(paths)) |
|
| 161 |
+ |
|
| 162 |
+ // Choose a "canonical" file. Really, it is just the first file to sort |
|
| 163 |
+ // against. We also effectively select the very first digest as the |
|
| 164 |
+ // "canonical" one for this file. |
|
| 165 |
+ first := bypath[paths[0]][0] |
|
| 166 |
+ |
|
| 167 |
+ resource := resource{
|
|
| 168 |
+ paths: paths, |
|
| 169 |
+ mode: first.Mode(), |
|
| 170 |
+ uid: first.UID(), |
|
| 171 |
+ gid: first.GID(), |
|
| 172 |
+ xattrs: xattrs, |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ switch typedF := first.(type) {
|
|
| 176 |
+ case RegularFile: |
|
| 177 |
+ var err error |
|
| 178 |
+ digests, err = uniqifyDigests(digests...) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return nil, err |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ return ®ularFile{
|
|
| 184 |
+ resource: resource, |
|
| 185 |
+ size: typedF.Size(), |
|
| 186 |
+ digests: digests, |
|
| 187 |
+ }, nil |
|
| 188 |
+ case Device: |
|
| 189 |
+ return &device{
|
|
| 190 |
+ resource: resource, |
|
| 191 |
+ major: typedF.Major(), |
|
| 192 |
+ minor: typedF.Minor(), |
|
| 193 |
+ }, nil |
|
| 194 |
+ |
|
| 195 |
+ case NamedPipe: |
|
| 196 |
+ return &namedPipe{
|
|
| 197 |
+ resource: resource, |
|
| 198 |
+ }, nil |
|
| 199 |
+ |
|
| 200 |
+ default: |
|
| 201 |
+ return nil, errNotAHardLink |
|
| 202 |
+ } |
|
| 203 |
+} |
|
| 204 |
+ |
|
| 205 |
+type Directory interface {
|
|
| 206 |
+ Resource |
|
| 207 |
+ XAttrer |
|
| 208 |
+ |
|
| 209 |
+ // Directory is a no-op method to identify directory objects by interface. |
|
| 210 |
+ Directory() |
|
| 211 |
+} |
|
| 212 |
+ |
|
| 213 |
+type SymLink interface {
|
|
| 214 |
+ Resource |
|
| 215 |
+ |
|
| 216 |
+ // Target returns the target of the symlink contained in the . |
|
| 217 |
+ Target() string |
|
| 218 |
+} |
|
| 219 |
+ |
|
| 220 |
+type NamedPipe interface {
|
|
| 221 |
+ Resource |
|
| 222 |
+ Hardlinkable |
|
| 223 |
+ XAttrer |
|
| 224 |
+ |
|
| 225 |
+ // Pipe is a no-op method to allow consistent resolution of NamedPipe |
|
| 226 |
+ // interface. |
|
| 227 |
+ Pipe() |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+type Device interface {
|
|
| 231 |
+ Resource |
|
| 232 |
+ Hardlinkable |
|
| 233 |
+ XAttrer |
|
| 234 |
+ |
|
| 235 |
+ Major() uint64 |
|
| 236 |
+ Minor() uint64 |
|
| 237 |
+} |
|
| 238 |
+ |
|
| 239 |
+type resource struct {
|
|
| 240 |
+ paths []string |
|
| 241 |
+ mode os.FileMode |
|
| 242 |
+ uid, gid int64 |
|
| 243 |
+ xattrs map[string][]byte |
|
| 244 |
+} |
|
| 245 |
+ |
|
| 246 |
+var _ Resource = &resource{}
|
|
| 247 |
+ |
|
| 248 |
+func (r *resource) Path() string {
|
|
| 249 |
+ if len(r.paths) < 1 {
|
|
| 250 |
+ return "" |
|
| 251 |
+ } |
|
| 252 |
+ |
|
| 253 |
+ return r.paths[0] |
|
| 254 |
+} |
|
| 255 |
+ |
|
| 256 |
+func (r *resource) Mode() os.FileMode {
|
|
| 257 |
+ return r.mode |
|
| 258 |
+} |
|
| 259 |
+ |
|
| 260 |
+func (r *resource) UID() int64 {
|
|
| 261 |
+ return r.uid |
|
| 262 |
+} |
|
| 263 |
+ |
|
| 264 |
+func (r *resource) GID() int64 {
|
|
| 265 |
+ return r.gid |
|
| 266 |
+} |
|
| 267 |
+ |
|
| 268 |
+type regularFile struct {
|
|
| 269 |
+ resource |
|
| 270 |
+ size int64 |
|
| 271 |
+ digests []digest.Digest |
|
| 272 |
+} |
|
| 273 |
+ |
|
| 274 |
+var _ RegularFile = ®ularFile{}
|
|
| 275 |
+ |
|
| 276 |
+// newRegularFile returns the RegularFile, using the populated base resource |
|
| 277 |
+// and one or more digests of the content. |
|
| 278 |
+func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
|
|
| 279 |
+ if !base.Mode().IsRegular() {
|
|
| 280 |
+ return nil, fmt.Errorf("not a regular file")
|
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ base.paths = make([]string, len(paths)) |
|
| 284 |
+ copy(base.paths, paths) |
|
| 285 |
+ |
|
| 286 |
+ // make our own copy of digests |
|
| 287 |
+ ds := make([]digest.Digest, len(dgsts)) |
|
| 288 |
+ copy(ds, dgsts) |
|
| 289 |
+ |
|
| 290 |
+ return ®ularFile{
|
|
| 291 |
+ resource: base, |
|
| 292 |
+ size: size, |
|
| 293 |
+ digests: ds, |
|
| 294 |
+ }, nil |
|
| 295 |
+} |
|
| 296 |
+ |
|
| 297 |
+func (rf *regularFile) Paths() []string {
|
|
| 298 |
+ paths := make([]string, len(rf.paths)) |
|
| 299 |
+ copy(paths, rf.paths) |
|
| 300 |
+ return paths |
|
| 301 |
+} |
|
| 302 |
+ |
|
| 303 |
+func (rf *regularFile) Size() int64 {
|
|
| 304 |
+ return rf.size |
|
| 305 |
+} |
|
| 306 |
+ |
|
| 307 |
+func (rf *regularFile) Digests() []digest.Digest {
|
|
| 308 |
+ digests := make([]digest.Digest, len(rf.digests)) |
|
| 309 |
+ copy(digests, rf.digests) |
|
| 310 |
+ return digests |
|
| 311 |
+} |
|
| 312 |
+ |
|
| 313 |
+func (rf *regularFile) XAttrs() map[string][]byte {
|
|
| 314 |
+ xattrs := make(map[string][]byte, len(rf.xattrs)) |
|
| 315 |
+ |
|
| 316 |
+ for attr, value := range rf.xattrs {
|
|
| 317 |
+ xattrs[attr] = append(xattrs[attr], value...) |
|
| 318 |
+ } |
|
| 319 |
+ |
|
| 320 |
+ return xattrs |
|
| 321 |
+} |
|
| 322 |
+ |
|
| 323 |
+type directory struct {
|
|
| 324 |
+ resource |
|
| 325 |
+} |
|
| 326 |
+ |
|
| 327 |
+var _ Directory = &directory{}
|
|
| 328 |
+ |
|
| 329 |
+func newDirectory(base resource) (Directory, error) {
|
|
| 330 |
+ if !base.Mode().IsDir() {
|
|
| 331 |
+ return nil, fmt.Errorf("not a directory")
|
|
| 332 |
+ } |
|
| 333 |
+ |
|
| 334 |
+ return &directory{
|
|
| 335 |
+ resource: base, |
|
| 336 |
+ }, nil |
|
| 337 |
+} |
|
| 338 |
+ |
|
| 339 |
+func (d *directory) Directory() {}
|
|
| 340 |
+ |
|
| 341 |
+func (d *directory) XAttrs() map[string][]byte {
|
|
| 342 |
+ xattrs := make(map[string][]byte, len(d.xattrs)) |
|
| 343 |
+ |
|
| 344 |
+ for attr, value := range d.xattrs {
|
|
| 345 |
+ xattrs[attr] = append(xattrs[attr], value...) |
|
| 346 |
+ } |
|
| 347 |
+ |
|
| 348 |
+ return xattrs |
|
| 349 |
+} |
|
| 350 |
+ |
|
| 351 |
+type symLink struct {
|
|
| 352 |
+ resource |
|
| 353 |
+ target string |
|
| 354 |
+} |
|
| 355 |
+ |
|
| 356 |
+var _ SymLink = &symLink{}
|
|
| 357 |
+ |
|
| 358 |
+func newSymLink(base resource, target string) (SymLink, error) {
|
|
| 359 |
+ if base.Mode()&os.ModeSymlink == 0 {
|
|
| 360 |
+ return nil, fmt.Errorf("not a symlink")
|
|
| 361 |
+ } |
|
| 362 |
+ |
|
| 363 |
+ return &symLink{
|
|
| 364 |
+ resource: base, |
|
| 365 |
+ target: target, |
|
| 366 |
+ }, nil |
|
| 367 |
+} |
|
| 368 |
+ |
|
| 369 |
+func (l *symLink) Target() string {
|
|
| 370 |
+ return l.target |
|
| 371 |
+} |
|
| 372 |
+ |
|
| 373 |
+type namedPipe struct {
|
|
| 374 |
+ resource |
|
| 375 |
+} |
|
| 376 |
+ |
|
| 377 |
+var _ NamedPipe = &namedPipe{}
|
|
| 378 |
+ |
|
| 379 |
+func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
|
|
| 380 |
+ if base.Mode()&os.ModeNamedPipe == 0 {
|
|
| 381 |
+ return nil, fmt.Errorf("not a namedpipe")
|
|
| 382 |
+ } |
|
| 383 |
+ |
|
| 384 |
+ base.paths = make([]string, len(paths)) |
|
| 385 |
+ copy(base.paths, paths) |
|
| 386 |
+ |
|
| 387 |
+ return &namedPipe{
|
|
| 388 |
+ resource: base, |
|
| 389 |
+ }, nil |
|
| 390 |
+} |
|
| 391 |
+ |
|
| 392 |
+func (np *namedPipe) Pipe() {}
|
|
| 393 |
+ |
|
| 394 |
+func (np *namedPipe) Paths() []string {
|
|
| 395 |
+ paths := make([]string, len(np.paths)) |
|
| 396 |
+ copy(paths, np.paths) |
|
| 397 |
+ return paths |
|
| 398 |
+} |
|
| 399 |
+ |
|
| 400 |
+func (np *namedPipe) XAttrs() map[string][]byte {
|
|
| 401 |
+ xattrs := make(map[string][]byte, len(np.xattrs)) |
|
| 402 |
+ |
|
| 403 |
+ for attr, value := range np.xattrs {
|
|
| 404 |
+ xattrs[attr] = append(xattrs[attr], value...) |
|
| 405 |
+ } |
|
| 406 |
+ |
|
| 407 |
+ return xattrs |
|
| 408 |
+} |
|
| 409 |
+ |
|
| 410 |
+type device struct {
|
|
| 411 |
+ resource |
|
| 412 |
+ major, minor uint64 |
|
| 413 |
+} |
|
| 414 |
+ |
|
| 415 |
+var _ Device = &device{}
|
|
| 416 |
+ |
|
| 417 |
+func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
|
|
| 418 |
+ if base.Mode()&os.ModeDevice == 0 {
|
|
| 419 |
+ return nil, fmt.Errorf("not a device")
|
|
| 420 |
+ } |
|
| 421 |
+ |
|
| 422 |
+ base.paths = make([]string, len(paths)) |
|
| 423 |
+ copy(base.paths, paths) |
|
| 424 |
+ |
|
| 425 |
+ return &device{
|
|
| 426 |
+ resource: base, |
|
| 427 |
+ major: major, |
|
| 428 |
+ minor: minor, |
|
| 429 |
+ }, nil |
|
| 430 |
+} |
|
| 431 |
+ |
|
| 432 |
+func (d *device) Paths() []string {
|
|
| 433 |
+ paths := make([]string, len(d.paths)) |
|
| 434 |
+ copy(paths, d.paths) |
|
| 435 |
+ return paths |
|
| 436 |
+} |
|
| 437 |
+ |
|
| 438 |
+func (d *device) XAttrs() map[string][]byte {
|
|
| 439 |
+ xattrs := make(map[string][]byte, len(d.xattrs)) |
|
| 440 |
+ |
|
| 441 |
+ for attr, value := range d.xattrs {
|
|
| 442 |
+ xattrs[attr] = append(xattrs[attr], value...) |
|
| 443 |
+ } |
|
| 444 |
+ |
|
| 445 |
+ return xattrs |
|
| 446 |
+} |
|
| 447 |
+ |
|
| 448 |
+func (d device) Major() uint64 {
|
|
| 449 |
+ return d.major |
|
| 450 |
+} |
|
| 451 |
+ |
|
| 452 |
+func (d device) Minor() uint64 {
|
|
| 453 |
+ return d.minor |
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+// toProto converts a resource to a protobuf record. We'd like to push this |
|
| 457 |
+// the individual types but we want to keep this all together during |
|
| 458 |
+// prototyping. |
|
| 459 |
+func toProto(resource Resource) *pb.Resource {
|
|
| 460 |
+ b := &pb.Resource{
|
|
| 461 |
+ Path: []string{resource.Path()},
|
|
| 462 |
+ Mode: uint32(resource.Mode()), |
|
| 463 |
+ Uid: resource.UID(), |
|
| 464 |
+ Gid: resource.GID(), |
|
| 465 |
+ } |
|
| 466 |
+ |
|
| 467 |
+ if xattrer, ok := resource.(XAttrer); ok {
|
|
| 468 |
+ // Sorts the XAttrs by name for consistent ordering. |
|
| 469 |
+ keys := []string{}
|
|
| 470 |
+ xattrs := xattrer.XAttrs() |
|
| 471 |
+ for k := range xattrs {
|
|
| 472 |
+ keys = append(keys, k) |
|
| 473 |
+ } |
|
| 474 |
+ sort.Strings(keys) |
|
| 475 |
+ |
|
| 476 |
+ for _, k := range keys {
|
|
| 477 |
+ b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
|
|
| 478 |
+ } |
|
| 479 |
+ } |
|
| 480 |
+ |
|
| 481 |
+ switch r := resource.(type) {
|
|
| 482 |
+ case RegularFile: |
|
| 483 |
+ b.Path = r.Paths() |
|
| 484 |
+ b.Size = uint64(r.Size()) |
|
| 485 |
+ |
|
| 486 |
+ for _, dgst := range r.Digests() {
|
|
| 487 |
+ b.Digest = append(b.Digest, dgst.String()) |
|
| 488 |
+ } |
|
| 489 |
+ case SymLink: |
|
| 490 |
+ b.Target = r.Target() |
|
| 491 |
+ case Device: |
|
| 492 |
+ b.Major, b.Minor = r.Major(), r.Minor() |
|
| 493 |
+ b.Path = r.Paths() |
|
| 494 |
+ case NamedPipe: |
|
| 495 |
+ b.Path = r.Paths() |
|
| 496 |
+ } |
|
| 497 |
+ |
|
| 498 |
+ // enforce a few stability guarantees that may not be provided by the |
|
| 499 |
+ // resource implementation. |
|
| 500 |
+ sort.Strings(b.Path) |
|
| 501 |
+ |
|
| 502 |
+ return b |
|
| 503 |
+} |
|
| 504 |
+ |
|
| 505 |
+// fromProto converts from a protobuf Resource to a Resource interface. |
|
| 506 |
+func fromProto(b *pb.Resource) (Resource, error) {
|
|
| 507 |
+ base := &resource{
|
|
| 508 |
+ paths: b.Path, |
|
| 509 |
+ mode: os.FileMode(b.Mode), |
|
| 510 |
+ uid: b.Uid, |
|
| 511 |
+ gid: b.Gid, |
|
| 512 |
+ } |
|
| 513 |
+ |
|
| 514 |
+ base.xattrs = make(map[string][]byte, len(b.Xattr)) |
|
| 515 |
+ |
|
| 516 |
+ for _, attr := range b.Xattr {
|
|
| 517 |
+ base.xattrs[attr.Name] = attr.Data |
|
| 518 |
+ } |
|
| 519 |
+ |
|
| 520 |
+ switch {
|
|
| 521 |
+ case base.Mode().IsRegular(): |
|
| 522 |
+ dgsts := make([]digest.Digest, len(b.Digest)) |
|
| 523 |
+ for i, dgst := range b.Digest {
|
|
| 524 |
+ // TODO(stevvooe): Should we be validating at this point? |
|
| 525 |
+ dgsts[i] = digest.Digest(dgst) |
|
| 526 |
+ } |
|
| 527 |
+ |
|
| 528 |
+ return newRegularFile(*base, b.Path, int64(b.Size), dgsts...) |
|
| 529 |
+ case base.Mode().IsDir(): |
|
| 530 |
+ return newDirectory(*base) |
|
| 531 |
+ case base.Mode()&os.ModeSymlink != 0: |
|
| 532 |
+ return newSymLink(*base, b.Target) |
|
| 533 |
+ case base.Mode()&os.ModeNamedPipe != 0: |
|
| 534 |
+ return newNamedPipe(*base, b.Path) |
|
| 535 |
+ case base.Mode()&os.ModeDevice != 0: |
|
| 536 |
+ return newDevice(*base, b.Path, b.Major, b.Minor) |
|
| 537 |
+ } |
|
| 538 |
+ |
|
| 539 |
+ return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
|
|
| 540 |
+} |
|
| 541 |
+ |
|
| 542 |
+// NOTE(stevvooe): An alternative model that supports inline declaration. |
|
| 543 |
+// Convenient for unit testing where inline declarations may be desirable but |
|
| 544 |
+// creates an awkward API for the standard use case. |
|
| 545 |
+ |
|
| 546 |
+// type ResourceKind int |
|
| 547 |
+ |
|
| 548 |
+// const ( |
|
| 549 |
+// ResourceRegularFile = iota + 1 |
|
| 550 |
+// ResourceDirectory |
|
| 551 |
+// ResourceSymLink |
|
| 552 |
+// Resource |
|
| 553 |
+// ) |
|
| 554 |
+ |
|
| 555 |
+// type Resource struct {
|
|
| 556 |
+// Kind ResourceKind |
|
| 557 |
+// Paths []string |
|
| 558 |
+// Mode os.FileMode |
|
| 559 |
+// UID string |
|
| 560 |
+// GID string |
|
| 561 |
+// Size int64 |
|
| 562 |
+// Digests []digest.Digest |
|
| 563 |
+// Target string |
|
| 564 |
+// Major, Minor int |
|
| 565 |
+// XAttrs map[string][]byte |
|
| 566 |
+// } |
|
| 567 |
+ |
|
| 568 |
+// type RegularFile struct {
|
|
| 569 |
+// Paths []string |
|
| 570 |
+// Size int64 |
|
| 571 |
+// Digests []digest.Digest |
|
| 572 |
+// Perm os.FileMode // os.ModePerm + sticky, setuid, setgid |
|
| 573 |
+// } |
| 0 | 574 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+// +build linux darwin freebsd solaris |
|
| 1 |
+ |
|
| 2 |
+package continuity |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "os" |
|
| 7 |
+ "syscall" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// newBaseResource returns a *resource, populated with data from p and fi, |
|
| 11 |
+// where p will be populated directly. |
|
| 12 |
+func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
|
|
| 13 |
+ // TODO(stevvooe): This need to be resolved for the container's root, |
|
| 14 |
+ // where here we are really getting the host OS's value. We need to allow |
|
| 15 |
+ // this be passed in and fixed up to make these uid/gid mappings portable. |
|
| 16 |
+ // Either this can be part of the driver or we can achieve it through some |
|
| 17 |
+ // other mechanism. |
|
| 18 |
+ sys, ok := fi.Sys().(*syscall.Stat_t) |
|
| 19 |
+ if !ok {
|
|
| 20 |
+ // TODO(stevvooe): This may not be a hard error for all platforms. We |
|
| 21 |
+ // may want to move this to the driver. |
|
| 22 |
+ return nil, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi)
|
|
| 23 |
+ } |
|
| 24 |
+ |
|
| 25 |
+ return &resource{
|
|
| 26 |
+ paths: []string{p},
|
|
| 27 |
+ mode: fi.Mode(), |
|
| 28 |
+ |
|
| 29 |
+ uid: int64(sys.Uid), |
|
| 30 |
+ gid: int64(sys.Gid), |
|
| 31 |
+ |
|
| 32 |
+ // NOTE(stevvooe): Population of shared xattrs field is deferred to |
|
| 33 |
+ // the resource types that populate it. Since they are a property of |
|
| 34 |
+ // the context, they must set there. |
|
| 35 |
+ }, nil |
|
| 36 |
+} |
| 0 | 37 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+package continuity |
|
| 1 |
+ |
|
| 2 |
+import "os" |
|
| 3 |
+ |
|
| 4 |
+// newBaseResource returns a *resource, populated with data from p and fi, |
|
| 5 |
+// where p will be populated directly. |
|
| 6 |
+func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
|
|
| 7 |
+ return &resource{
|
|
| 8 |
+ paths: []string{p},
|
|
| 9 |
+ mode: fi.Mode(), |
|
| 10 |
+ }, nil |
|
| 11 |
+} |
| ... | ... |
@@ -20,9 +20,6 @@ import ( |
| 20 | 20 |
"io" |
| 21 | 21 |
"os" |
| 22 | 22 |
"os/exec" |
| 23 |
- |
|
| 24 |
- "github.com/pkg/errors" |
|
| 25 |
- "golang.org/x/sys/unix" |
|
| 26 | 23 |
) |
| 27 | 24 |
|
| 28 | 25 |
type IO interface {
|
| ... | ... |
@@ -37,49 +34,22 @@ type StartCloser interface {
|
| 37 | 37 |
CloseAfterStart() error |
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 |
-// NewPipeIO creates pipe pairs to be used with runc |
|
| 41 |
-func NewPipeIO(uid, gid int) (i IO, err error) {
|
|
| 42 |
- var pipes []*pipe |
|
| 43 |
- // cleanup in case of an error |
|
| 44 |
- defer func() {
|
|
| 45 |
- if err != nil {
|
|
| 46 |
- for _, p := range pipes {
|
|
| 47 |
- p.Close() |
|
| 48 |
- } |
|
| 49 |
- } |
|
| 50 |
- }() |
|
| 51 |
- stdin, err := newPipe() |
|
| 52 |
- if err != nil {
|
|
| 53 |
- return nil, err |
|
| 54 |
- } |
|
| 55 |
- pipes = append(pipes, stdin) |
|
| 56 |
- if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
|
|
| 57 |
- return nil, errors.Wrap(err, "failed to chown stdin") |
|
| 58 |
- } |
|
| 40 |
+// IOOpt sets I/O creation options |
|
| 41 |
+type IOOpt func(*IOOption) |
|
| 59 | 42 |
|
| 60 |
- stdout, err := newPipe() |
|
| 61 |
- if err != nil {
|
|
| 62 |
- return nil, err |
|
| 63 |
- } |
|
| 64 |
- pipes = append(pipes, stdout) |
|
| 65 |
- if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
|
|
| 66 |
- return nil, errors.Wrap(err, "failed to chown stdout") |
|
| 67 |
- } |
|
| 43 |
+// IOOption holds I/O creation options |
|
| 44 |
+type IOOption struct {
|
|
| 45 |
+ OpenStdin bool |
|
| 46 |
+ OpenStdout bool |
|
| 47 |
+ OpenStderr bool |
|
| 48 |
+} |
|
| 68 | 49 |
|
| 69 |
- stderr, err := newPipe() |
|
| 70 |
- if err != nil {
|
|
| 71 |
- return nil, err |
|
| 50 |
+func defaultIOOption() *IOOption {
|
|
| 51 |
+ return &IOOption{
|
|
| 52 |
+ OpenStdin: true, |
|
| 53 |
+ OpenStdout: true, |
|
| 54 |
+ OpenStderr: true, |
|
| 72 | 55 |
} |
| 73 |
- pipes = append(pipes, stderr) |
|
| 74 |
- if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
|
|
| 75 |
- return nil, errors.Wrap(err, "failed to chown stderr") |
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- return &pipeIO{
|
|
| 79 |
- in: stdin, |
|
| 80 |
- out: stdout, |
|
| 81 |
- err: stderr, |
|
| 82 |
- }, nil |
|
| 83 | 56 |
} |
| 84 | 57 |
|
| 85 | 58 |
func newPipe() (*pipe, error) {
|
| ... | ... |
@@ -99,9 +69,9 @@ type pipe struct {
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 | 101 |
func (p *pipe) Close() error {
|
| 102 |
- err := p.r.Close() |
|
| 103 |
- if werr := p.w.Close(); err == nil {
|
|
| 104 |
- err = werr |
|
| 102 |
+ err := p.w.Close() |
|
| 103 |
+ if rerr := p.r.Close(); err == nil {
|
|
| 104 |
+ err = rerr |
|
| 105 | 105 |
} |
| 106 | 106 |
return err |
| 107 | 107 |
} |
| ... | ... |
@@ -113,14 +83,23 @@ type pipeIO struct {
|
| 113 | 113 |
} |
| 114 | 114 |
|
| 115 | 115 |
func (i *pipeIO) Stdin() io.WriteCloser {
|
| 116 |
+ if i.in == nil {
|
|
| 117 |
+ return nil |
|
| 118 |
+ } |
|
| 116 | 119 |
return i.in.w |
| 117 | 120 |
} |
| 118 | 121 |
|
| 119 | 122 |
func (i *pipeIO) Stdout() io.ReadCloser {
|
| 123 |
+ if i.out == nil {
|
|
| 124 |
+ return nil |
|
| 125 |
+ } |
|
| 120 | 126 |
return i.out.r |
| 121 | 127 |
} |
| 122 | 128 |
|
| 123 | 129 |
func (i *pipeIO) Stderr() io.ReadCloser {
|
| 130 |
+ if i.err == nil {
|
|
| 131 |
+ return nil |
|
| 132 |
+ } |
|
| 124 | 133 |
return i.err.r |
| 125 | 134 |
} |
| 126 | 135 |
|
| ... | ... |
@@ -131,28 +110,38 @@ func (i *pipeIO) Close() error {
|
| 131 | 131 |
i.out, |
| 132 | 132 |
i.err, |
| 133 | 133 |
} {
|
| 134 |
- if cerr := v.Close(); err == nil {
|
|
| 135 |
- err = cerr |
|
| 134 |
+ if v != nil {
|
|
| 135 |
+ if cerr := v.Close(); err == nil {
|
|
| 136 |
+ err = cerr |
|
| 137 |
+ } |
|
| 136 | 138 |
} |
| 137 | 139 |
} |
| 138 | 140 |
return err |
| 139 | 141 |
} |
| 140 | 142 |
|
| 141 | 143 |
func (i *pipeIO) CloseAfterStart() error {
|
| 142 |
- for _, f := range []*os.File{
|
|
| 143 |
- i.out.w, |
|
| 144 |
- i.err.w, |
|
| 144 |
+ for _, f := range []*pipe{
|
|
| 145 |
+ i.out, |
|
| 146 |
+ i.err, |
|
| 145 | 147 |
} {
|
| 146 |
- f.Close() |
|
| 148 |
+ if f != nil {
|
|
| 149 |
+ f.w.Close() |
|
| 150 |
+ } |
|
| 147 | 151 |
} |
| 148 | 152 |
return nil |
| 149 | 153 |
} |
| 150 | 154 |
|
| 151 | 155 |
// Set sets the io to the exec.Cmd |
| 152 | 156 |
func (i *pipeIO) Set(cmd *exec.Cmd) {
|
| 153 |
- cmd.Stdin = i.in.r |
|
| 154 |
- cmd.Stdout = i.out.w |
|
| 155 |
- cmd.Stderr = i.err.w |
|
| 157 |
+ if i.in != nil {
|
|
| 158 |
+ cmd.Stdin = i.in.r |
|
| 159 |
+ } |
|
| 160 |
+ if i.out != nil {
|
|
| 161 |
+ cmd.Stdout = i.out.w |
|
| 162 |
+ } |
|
| 163 |
+ if i.err != nil {
|
|
| 164 |
+ cmd.Stderr = i.err.w |
|
| 165 |
+ } |
|
| 156 | 166 |
} |
| 157 | 167 |
|
| 158 | 168 |
func NewSTDIO() (IO, error) {
|
| 159 | 169 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,76 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+ Copyright The containerd Authors. |
|
| 4 |
+ |
|
| 5 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 6 |
+ you may not use this file except in compliance with the License. |
|
| 7 |
+ You may obtain a copy of the License at |
|
| 8 |
+ |
|
| 9 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 10 |
+ |
|
| 11 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 12 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 13 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 14 |
+ See the License for the specific language governing permissions and |
|
| 15 |
+ limitations under the License. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+package runc |
|
| 19 |
+ |
|
| 20 |
+import ( |
|
| 21 |
+ "github.com/pkg/errors" |
|
| 22 |
+ "golang.org/x/sys/unix" |
|
| 23 |
+) |
|
| 24 |
+ |
|
| 25 |
+// NewPipeIO creates pipe pairs to be used with runc |
|
| 26 |
+func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) {
|
|
| 27 |
+ option := defaultIOOption() |
|
| 28 |
+ for _, o := range opts {
|
|
| 29 |
+ o(option) |
|
| 30 |
+ } |
|
| 31 |
+ var ( |
|
| 32 |
+ pipes []*pipe |
|
| 33 |
+ stdin, stdout, stderr *pipe |
|
| 34 |
+ ) |
|
| 35 |
+ // cleanup in case of an error |
|
| 36 |
+ defer func() {
|
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ for _, p := range pipes {
|
|
| 39 |
+ p.Close() |
|
| 40 |
+ } |
|
| 41 |
+ } |
|
| 42 |
+ }() |
|
| 43 |
+ if option.OpenStdin {
|
|
| 44 |
+ if stdin, err = newPipe(); err != nil {
|
|
| 45 |
+ return nil, err |
|
| 46 |
+ } |
|
| 47 |
+ pipes = append(pipes, stdin) |
|
| 48 |
+ if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
|
|
| 49 |
+ return nil, errors.Wrap(err, "failed to chown stdin") |
|
| 50 |
+ } |
|
| 51 |
+ } |
|
| 52 |
+ if option.OpenStdout {
|
|
| 53 |
+ if stdout, err = newPipe(); err != nil {
|
|
| 54 |
+ return nil, err |
|
| 55 |
+ } |
|
| 56 |
+ pipes = append(pipes, stdout) |
|
| 57 |
+ if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
|
|
| 58 |
+ return nil, errors.Wrap(err, "failed to chown stdout") |
|
| 59 |
+ } |
|
| 60 |
+ } |
|
| 61 |
+ if option.OpenStderr {
|
|
| 62 |
+ if stderr, err = newPipe(); err != nil {
|
|
| 63 |
+ return nil, err |
|
| 64 |
+ } |
|
| 65 |
+ pipes = append(pipes, stderr) |
|
| 66 |
+ if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
|
|
| 67 |
+ return nil, errors.Wrap(err, "failed to chown stderr") |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ return &pipeIO{
|
|
| 71 |
+ in: stdin, |
|
| 72 |
+ out: stdout, |
|
| 73 |
+ err: stderr, |
|
| 74 |
+ }, nil |
|
| 75 |
+} |
| 0 | 76 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,62 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+ Copyright The containerd Authors. |
|
| 4 |
+ |
|
| 5 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 6 |
+ you may not use this file except in compliance with the License. |
|
| 7 |
+ You may obtain a copy of the License at |
|
| 8 |
+ |
|
| 9 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 10 |
+ |
|
| 11 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 12 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 13 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 14 |
+ See the License for the specific language governing permissions and |
|
| 15 |
+ limitations under the License. |
|
| 16 |
+*/ |
|
| 17 |
+ |
|
| 18 |
+package runc |
|
| 19 |
+ |
|
| 20 |
+// NewPipeIO creates pipe pairs to be used with runc |
|
| 21 |
+func NewPipeIO(opts ...IOOpt) (i IO, err error) {
|
|
| 22 |
+ option := defaultIOOption() |
|
| 23 |
+ for _, o := range opts {
|
|
| 24 |
+ o(option) |
|
| 25 |
+ } |
|
| 26 |
+ var ( |
|
| 27 |
+ pipes []*pipe |
|
| 28 |
+ stdin, stdout, stderr *pipe |
|
| 29 |
+ ) |
|
| 30 |
+ // cleanup in case of an error |
|
| 31 |
+ defer func() {
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ for _, p := range pipes {
|
|
| 34 |
+ p.Close() |
|
| 35 |
+ } |
|
| 36 |
+ } |
|
| 37 |
+ }() |
|
| 38 |
+ if option.OpenStdin {
|
|
| 39 |
+ if stdin, err = newPipe(); err != nil {
|
|
| 40 |
+ return nil, err |
|
| 41 |
+ } |
|
| 42 |
+ pipes = append(pipes, stdin) |
|
| 43 |
+ } |
|
| 44 |
+ if option.OpenStdout {
|
|
| 45 |
+ if stdout, err = newPipe(); err != nil {
|
|
| 46 |
+ return nil, err |
|
| 47 |
+ } |
|
| 48 |
+ pipes = append(pipes, stdout) |
|
| 49 |
+ } |
|
| 50 |
+ if option.OpenStderr {
|
|
| 51 |
+ if stderr, err = newPipe(); err != nil {
|
|
| 52 |
+ return nil, err |
|
| 53 |
+ } |
|
| 54 |
+ pipes = append(pipes, stderr) |
|
| 55 |
+ } |
|
| 56 |
+ return &pipeIO{
|
|
| 57 |
+ in: stdin, |
|
| 58 |
+ out: stdout, |
|
| 59 |
+ err: stderr, |
|
| 60 |
+ }, nil |
|
| 61 |
+} |
| ... | ... |
@@ -608,9 +608,8 @@ func parseVersion(data []byte) (Version, error) {
|
| 608 | 608 |
var v Version |
| 609 | 609 |
parts := strings.Split(strings.TrimSpace(string(data)), "\n") |
| 610 | 610 |
if len(parts) != 3 {
|
| 611 |
- return v, ErrParseRuncVersion |
|
| 611 |
+ return v, nil |
|
| 612 | 612 |
} |
| 613 |
- |
|
| 614 | 613 |
for i, p := range []struct {
|
| 615 | 614 |
dest *string |
| 616 | 615 |
split string |