diff options
Diffstat (limited to 'pkg')
24 files changed, 605 insertions, 147 deletions
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go index 223eb2cd5..d8197415c 100644 --- a/pkg/api/handlers/compat/containers_archive.go +++ b/pkg/api/handlers/compat/containers_archive.go @@ -1,13 +1,8 @@ package compat import ( - "bytes" - "encoding/base64" - "encoding/json" "fmt" "net/http" - "os" - "time" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" @@ -15,6 +10,7 @@ import ( "github.com/containers/podman/v2/pkg/copy" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func Archive(w http.ResponseWriter, r *http.Request) { @@ -71,12 +67,12 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrapf(err, "error stating container path %q", query.Path)) return } - statHeader, err := fileInfoToDockerStats(info) + statHeader, err := copy.EncodeFileInfo(info) if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - w.Header().Add("X-Docker-Container-Path-Stat", statHeader) + w.Header().Add(copy.XDockerContainerPathStatHeader, statHeader) // Our work is done when the user is interested in the header only. if r.Method == http.MethodHead { @@ -91,47 +87,16 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De return } - w.WriteHeader(http.StatusOK) - if err := copy.Copy(&source, &destination, false); err != nil { + copier, err := copy.GetCopier(&source, &destination, false) + if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } -} - -func fileInfoToDockerStats(info *copy.FileInfo) (string, error) { - dockerStats := struct { - Name string `json:"name"` - Size int64 `json:"size"` - Mode os.FileMode `json:"mode"` - ModTime time.Time `json:"mtime"` - LinkTarget string `json:"linkTarget"` - }{ - Name: info.Name, - Size: info.Size, - Mode: info.Mode, - ModTime: info.ModTime, - LinkTarget: info.LinkTarget, - } - - jsonBytes, err := json.Marshal(&dockerStats) - if err != nil { - return "", errors.Wrap(err, "failed to serialize file stats") - } - - buff := bytes.NewBuffer(make([]byte, 0, 128)) - base64encoder := base64.NewEncoder(base64.StdEncoding, buff) - - _, err = base64encoder.Write(jsonBytes) - if err != nil { - return "", err - } - - err = base64encoder.Close() - if err != nil { - return "", err + w.WriteHeader(http.StatusOK) + if err := copier.Copy(); err != nil { + logrus.Errorf("Error during copy: %v", err) + return } - - return buff.String(), nil } func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) { @@ -170,9 +135,14 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, return } - w.WriteHeader(http.StatusOK) - if err := copy.Copy(&source, &destination, false); err != nil { + copier, err := copy.GetCopier(&source, &destination, false) + if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } + w.WriteHeader(http.StatusOK) + if err := copier.Copy(); err != nil { + logrus.Errorf("Error during copy: %v", err) + return + } } diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 43478c1d3..415ff85cd 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -71,6 +71,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { ForceRm bool `schema:"forcerm"` HTTPProxy bool `schema:"httpproxy"` Labels string `schema:"labels"` + Layers bool `schema:"layers"` MemSwap int64 `schema:"memswap"` Memory int64 `schema:"memory"` NetworkMode string `schema:"networkmode"` @@ -165,6 +166,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Registry: query.Registry, IgnoreUnrecognizedInstructions: true, Quiet: query.Quiet, + Layers: query.Layers, Isolation: buildah.IsolationChroot, Compression: archive.Gzip, Args: buildArgs, diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index fe13971b0..f0b922885 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -131,7 +131,7 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt Name: conf.Name, ID: network.GetNetworkID(conf.Name), Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert - Scope: "", + Scope: "local", Driver: network.DefaultNetworkDriver, EnableIPv6: false, IPAM: dockerNetwork.IPAM{ @@ -197,7 +197,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { } var reports []*types.NetworkResource - logrus.Errorf("netNames: %q", strings.Join(netNames, ", ")) + logrus.Debugf("netNames: %q", strings.Join(netNames, ", ")) for _, name := range netNames { report, err := getNetworkResourceByNameOrID(name, runtime, query.Filters) if err != nil { @@ -239,7 +239,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { Internal: networkCreate.Internal, Labels: networkCreate.Labels, } - if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil { + if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 { if len(networkCreate.IPAM.Config) > 1 { utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config")) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 6145207ca..505c96126 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -51,7 +51,7 @@ func ImageExists(w http.ResponseWriter, r *http.Request) { return } if !report.Value { - utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(nil, "failed to find image %s", name)) + utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s", name)) return } utils.WriteResponse(w, http.StatusNoContent, "") diff --git a/pkg/bindings/generator/generator.go b/pkg/bindings/generator/generator.go new file mode 100644 index 000000000..24c2310ff --- /dev/null +++ b/pkg/bindings/generator/generator.go @@ -0,0 +1,234 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "os" + "os/exec" + "strings" + "text/template" + "time" +) + +var bodyTmpl = `package {{.PackageName}} + +import ( +{{range $import := .Imports}} {{$import}} +{{end}} + +) + +/* +This file is generated automatically by go generate. Do not edit. + +Created {{.Date}} +*/ + +// Changed +func (o *{{.StructName}}) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *{{.StructName}}) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch f.Kind() { + case reflect.Bool: + params.Set(fieldName, strconv.FormatBool(f.Bool())) + case reflect.String: + params.Set(fieldName, f.String()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + params.Set(fieldName, strconv.FormatInt(f.Int(), 10)) + case reflect.Slice: + typ := reflect.TypeOf(f.Interface()).Elem() + slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap()) + switch typ.Kind() { + case reflect.String: + s, ok := slice.Interface().([]string) + if !ok { + return nil, errors.New("failed to convert to string slice") + } + for _, val := range s { + params.Add(fieldName, val) + } + default: + return nil, errors.Errorf("unknown slice type %s", f.Kind().String()) + } + case reflect.Map: + lowerCaseKeys := make(map[string][]string) + // I dont know if this code is needed anymore, TBD + // for k, v := range filters { + // lowerCaseKeys[strings.ToLower(k)] = v + // } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + default: + return nil, errors.Errorf("unknown type %s", f.Kind().String()) + } + } + return params, nil +} +` + +var fieldTmpl = ` +// With{{.Name}} +func(o *{{.StructName}}) With{{.Name}}(value {{.Type}}) *{{.StructName}} { + v := &value + o.{{.Name}} = v + return o +} +` + +type fieldStruct struct { + Name string + StructName string + Type string +} + +func main() { + var ( + closed bool + fieldStructs []fieldStruct + structNode ast.Node + ) + srcFile := os.Getenv("GOFILE") + pkg := os.Getenv("GOPACKAGE") + inputStructName := os.Args[1] + b, err := ioutil.ReadFile(srcFile) + if err != nil { + panic(err) + } + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "", b, parser.ParseComments) + if err != nil { + panic(err) + } + + // always add reflect + imports := []string{"\"reflect\""} + for _, imp := range f.Imports { + imports = append(imports, imp.Path.Value) + } + + out, err := os.Create(strings.ToLower(inputStructName) + "_" + srcFile) + if err != nil { + panic(err) + } + defer func() { + if !closed { + out.Close() + } + }() + bodyStruct := struct { + PackageName string + Imports []string + Date string + StructName string + }{ + PackageName: pkg, + Imports: imports, + Date: time.Now().String(), + StructName: inputStructName, + } + + body := template.Must(template.New("body").Parse(bodyTmpl)) + fields := template.Must(template.New("fields").Parse(fieldTmpl)) + ast.Inspect(f, func(n ast.Node) bool { + ref, refOK := n.(*ast.TypeSpec) + if refOK { + if ref.Name.Name == inputStructName { + structNode = n + x := ref.Type.(*ast.StructType) + for _, field := range x.Fields.List { + var ( + name string + ) + typeExpr := field.Type + start := typeExpr.Pos() - 1 + end := typeExpr.End() - 1 + fieldType := strings.Replace(string(b[start:end]), "*", "", 1) + if len(field.Names) > 0 { + name = field.Names[0].Name + if len(name) < 1 { + panic(errors.New("bad name")) + } + } + fStruct := fieldStruct{ + Name: name, + StructName: inputStructName, + Type: fieldType, + } + fieldStructs = append(fieldStructs, fStruct) + } // for + + // create the body + if err := body.Execute(out, bodyStruct); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // create with func from the struct fields + for _, fs := range fieldStructs { + if err := fields.Execute(out, fs); err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + // close out file + if err := out.Close(); err != nil { + fmt.Println(err) + os.Exit(1) + } + closed = true + + // go fmt file + gofmt := exec.Command("gofmt", "-w", "-s", out.Name()) + gofmt.Stderr = os.Stdout + if err := gofmt.Run(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // go import file + goimport := exec.Command("goimports", "-w", out.Name()) + goimport.Stderr = os.Stdout + if err := goimport.Run(); err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + } + return true + }) +} diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 815ab4e86..d34ab87d9 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -41,6 +41,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if options.NoCache { params.Set("nocache", "1") } + if options.Layers { + params.Set("layers", "1") + } // TODO cachefrom if options.PullPolicy == buildah.PullAlways { params.Set("pull", "1") diff --git a/pkg/bindings/images/removeoptions_types.go b/pkg/bindings/images/removeoptions_types.go new file mode 100644 index 000000000..5902bf908 --- /dev/null +++ b/pkg/bindings/images/removeoptions_types.go @@ -0,0 +1,93 @@ +package images + +import ( + "net/url" + "reflect" + "strconv" + + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +/* +This file is generated automatically by go generate. Do not edit. + +Created 2020-12-10 12:51:06.090426622 -0600 CST m=+0.000133169 +*/ + +// Changed +func (o *RemoveOptions) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *RemoveOptions) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch f.Kind() { + case reflect.Bool: + params.Set(fieldName, strconv.FormatBool(f.Bool())) + case reflect.String: + params.Set(fieldName, f.String()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + params.Set(fieldName, strconv.FormatInt(f.Int(), 10)) + case reflect.Slice: + typ := reflect.TypeOf(f.Interface()).Elem() + slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap()) + switch typ.Kind() { + case reflect.String: + s, ok := slice.Interface().([]string) + if !ok { + return nil, errors.New("failed to convert to string slice") + } + for _, val := range s { + params.Add(fieldName, val) + } + default: + return nil, errors.Errorf("unknown slice type %s", f.Kind().String()) + } + case reflect.Map: + lowerCaseKeys := make(map[string][]string) + // I dont know if this code is needed anymore, TBD + // for k, v := range filters { + // lowerCaseKeys[strings.ToLower(k)] = v + // } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + default: + return nil, errors.Errorf("unknown type %s", f.Kind().String()) + } + } + return params, nil +} + +// WithForce +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + v := &value + o.Force = v + return o +} diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index 9685b75e4..0b3b88165 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -41,17 +41,19 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors) } -// Remove removes an image from the local storage. Use force to remove an +// Remove removes an image from the local storage. Use optional force option to remove an // image, even if it's used by containers. -func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) { +func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ImageRemoveReport, error) { var report handlers.LibpodImagesRemoveReport conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } - params := url.Values{} - params.Set("force", strconv.FormatBool(force)) + params, err := options.ToParams() + if err != nil { + return nil, err + } response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID) if err != nil { return nil, err diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go new file mode 100644 index 000000000..340c7bdb9 --- /dev/null +++ b/pkg/bindings/images/types.go @@ -0,0 +1,8 @@ +package images + +//go:generate go run ../generator/generator.go RemoveOptions +// RemoveOptions are optional options for image removal +type RemoveOptions struct { + // Forces removes all containers based on the image + Force *bool +} diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 7d9415f91..684f110e8 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -84,7 +84,7 @@ var _ = Describe("Podman images", func() { // Test to validate the remove image api It("remove image", func() { // Remove invalid image should be a 404 - response, err := images.Remove(bt.conn, "foobar5000", false) + response, err := images.Remove(bt.conn, "foobar5000", nil) Expect(err).ToNot(BeNil()) Expect(response).To(BeNil()) code, _ := bindings.CheckResponseCode(err) @@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() { // Remove an image by name, validate image is removed and error is nil inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil) Expect(err).To(BeNil()) - response, err = images.Remove(bt.conn, busybox.shortName, false) + response, err = images.Remove(bt.conn, busybox.shortName, nil) Expect(err).To(BeNil()) code, _ = bindings.CheckResponseCode(err) @@ -113,12 +113,13 @@ var _ = Describe("Podman images", func() { // try to remove the image "alpine". This should fail since we are not force // deleting hence image cannot be deleted until the container is deleted. - response, err = images.Remove(bt.conn, alpine.shortName, false) + response, err = images.Remove(bt.conn, alpine.shortName, nil) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusConflict)) // Removing the image "alpine" where force = true - response, err = images.Remove(bt.conn, alpine.shortName, true) + options := images.RemoveOptions{} + response, err = images.Remove(bt.conn, alpine.shortName, options.WithForce(true)) Expect(err).To(BeNil()) // To be extra sure, check if the previously created container // is gone as well. @@ -213,7 +214,7 @@ var _ = Describe("Podman images", func() { It("Load|Import Image", func() { // load an image - _, err := images.Remove(bt.conn, alpine.name, false) + _, err := images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err := images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -231,7 +232,7 @@ var _ = Describe("Podman images", func() { // load with a repo name f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) Expect(err).To(BeNil()) - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err = images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -247,7 +248,7 @@ var _ = Describe("Podman images", func() { // load with a bad repo name should trigger a 500 f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) Expect(err).To(BeNil()) - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err = images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -275,7 +276,7 @@ var _ = Describe("Podman images", func() { It("Import Image", func() { // load an image - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err := images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 55fc4cb0d..a4ecaa20f 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() { code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) - _, err = images.Remove(bt.conn, id, false) + _, err = images.Remove(bt.conn, id, nil) Expect(err).To(BeNil()) // create manifest list with images diff --git a/pkg/copy/copy.go b/pkg/copy/copy.go index 0e68eb450..13893deb2 100644 --- a/pkg/copy/copy.go +++ b/pkg/copy/copy.go @@ -25,31 +25,61 @@ import ( // // **************************************************************************** -// Copy the source item to destination. Use extract to untar the source if -// it's a tar archive. -func Copy(source *CopyItem, destination *CopyItem, extract bool) error { +// Copier copies data from a source to a destination CopyItem. +type Copier struct { + copyFunc func() error + cleanUpFuncs []deferFunc +} + +// cleanUp releases resources the Copier may hold open. +func (c *Copier) cleanUp() { + for _, f := range c.cleanUpFuncs { + f() + } +} + +// Copy data from a source to a destination CopyItem. +func (c *Copier) Copy() error { + defer c.cleanUp() + return c.copyFunc() +} + +// GetCopiers returns a Copier to copy the source item to destination. Use +// extract to untar the source if it's a tar archive. +func GetCopier(source *CopyItem, destination *CopyItem, extract bool) (*Copier, error) { + copier := &Copier{} + // First, do the man-page dance. See podman-cp(1) for details. if err := enforceCopyRules(source, destination); err != nil { - return err + return nil, err } // Destination is a stream (e.g., stdout or an http body). if destination.info.IsStream { // Source is a stream (e.g., stdin or an http body). if source.info.IsStream { - _, err := io.Copy(destination.writer, source.reader) - return err + copier.copyFunc = func() error { + _, err := io.Copy(destination.writer, source.reader) + return err + } + return copier, nil } root, glob, err := source.buildahGlobs() if err != nil { - return err + return nil, err } - return buildahCopiah.Get(root, "", source.getOptions(), []string{glob}, destination.writer) + copier.copyFunc = func() error { + return buildahCopiah.Get(root, "", source.getOptions(), []string{glob}, destination.writer) + } + return copier, nil } // Destination is either a file or a directory. if source.info.IsStream { - return buildahCopiah.Put(destination.root, destination.resolved, source.putOptions(), source.reader) + copier.copyFunc = func() error { + return buildahCopiah.Put(destination.root, destination.resolved, source.putOptions(), source.reader) + } + return copier, nil } tarOptions := &archive.TarOptions{ @@ -71,33 +101,36 @@ func Copy(source *CopyItem, destination *CopyItem, extract bool) error { var tarReader io.ReadCloser if extract && archive.IsArchivePath(source.resolved) { if !destination.info.IsDir { - return errors.Errorf("cannot extract archive %q to file %q", source.original, destination.original) + return nil, errors.Errorf("cannot extract archive %q to file %q", source.original, destination.original) } reader, err := os.Open(source.resolved) if err != nil { - return err + return nil, err } - defer reader.Close() + copier.cleanUpFuncs = append(copier.cleanUpFuncs, func() { reader.Close() }) // The stream from stdin may be compressed (e.g., via gzip). decompressedStream, err := archive.DecompressStream(reader) if err != nil { - return err + return nil, err } - defer decompressedStream.Close() + copier.cleanUpFuncs = append(copier.cleanUpFuncs, func() { decompressedStream.Close() }) tarReader = decompressedStream } else { reader, err := archive.TarWithOptions(source.resolved, tarOptions) if err != nil { - return err + return nil, err } - defer reader.Close() + copier.cleanUpFuncs = append(copier.cleanUpFuncs, func() { reader.Close() }) tarReader = reader } - return buildahCopiah.Put(root, dir, source.putOptions(), tarReader) + copier.copyFunc = func() error { + return buildahCopiah.Put(root, dir, source.putOptions(), tarReader) + } + return copier, nil } // enforceCopyRules enforces the rules for copying from a source to a @@ -114,7 +147,6 @@ func enforceCopyRules(source, destination *CopyItem) error { return nil } - // Source is a *stream*. if source.info.IsStream { if !(destination.info.IsDir || destination.info.IsStream) { return errors.New("destination must be a directory or stream when copying from a stream") diff --git a/pkg/copy/fileinfo.go b/pkg/copy/fileinfo.go new file mode 100644 index 000000000..08b4eb377 --- /dev/null +++ b/pkg/copy/fileinfo.go @@ -0,0 +1,56 @@ +package copy + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "os" + "strings" + "time" + + "github.com/pkg/errors" +) + +// XDockerContainerPathStatHeader is the *key* in http headers pointing to the +// base64 encoded JSON payload of stating a path in a container. +const XDockerContainerPathStatHeader = "X-Docker-Container-Path-Stat" + +// FileInfo describes a file or directory and is returned by +// (*CopyItem).Stat(). +type FileInfo struct { + Name string `json:"name"` + Size int64 `json:"size"` + Mode os.FileMode `json:"mode"` + ModTime time.Time `json:"mtime"` + IsDir bool `json:"isDir"` + IsStream bool `json:"isStream"` + LinkTarget string `json:"linkTarget"` +} + +// EncodeFileInfo serializes the specified FileInfo as a base64 encoded JSON +// payload. Intended for Docker compat. +func EncodeFileInfo(info *FileInfo) (string, error) { + buf, err := json.Marshal(&info) + if err != nil { + return "", errors.Wrap(err, "failed to serialize file stats") + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// ExtractFileInfoFromHeader extracts a base64 encoded JSON payload of a +// FileInfo in the http header. If no such header entry is found, nil is +// returned. Intended for Docker compat. +func ExtractFileInfoFromHeader(header *http.Header) (*FileInfo, error) { + rawData := header.Get(XDockerContainerPathStatHeader) + if len(rawData) == 0 { + return nil, nil + } + + info := FileInfo{} + base64Decoder := base64.NewDecoder(base64.URLEncoding, strings.NewReader(rawData)) + if err := json.NewDecoder(base64Decoder).Decode(&info); err != nil { + return nil, err + } + + return &info, nil +} diff --git a/pkg/copy/item.go b/pkg/copy/item.go index db6bca610..df8bf30b9 100644 --- a/pkg/copy/item.go +++ b/pkg/copy/item.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" "strings" - "time" buildahCopiah "github.com/containers/buildah/copier" "github.com/containers/buildah/pkg/chrootuser" @@ -75,18 +74,6 @@ type CopyItem struct { // deferFunc allows for returning functions that must be deferred at call sites. type deferFunc func() -// FileInfo describes a file or directory and is returned by -// (*CopyItem).Stat(). -type FileInfo struct { - Name string `json:"name"` - Size int64 `json:"size"` - Mode os.FileMode `json:"mode"` - ModTime time.Time `json:"mtime"` - IsDir bool `json:"isDir"` - IsStream bool `json:"isStream"` - LinkTarget string `json:"linkTarget"` -} - // Stat returns the FileInfo. func (item *CopyItem) Stat() (*FileInfo, error) { return &item.info, item.statError diff --git a/pkg/copy/parse.go b/pkg/copy/parse.go new file mode 100644 index 000000000..39e0e1547 --- /dev/null +++ b/pkg/copy/parse.go @@ -0,0 +1,61 @@ +package copy + +import ( + "strings" + + "github.com/pkg/errors" +) + +// ParseSourceAndDestination parses the source and destination input into a +// possibly specified container and path. The input format is described in +// podman-cp(1) as "[nameOrID:]path". Colons in paths are supported as long +// they start with a dot or slash. +// +// It returns, in order, the source container and path, followed by the +// destination container and path, and an error. Note that exactly one +// container must be specified. +func ParseSourceAndDestination(source, destination string) (string, string, string, string, error) { + sourceContainer, sourcePath := parseUserInput(source) + destContainer, destPath := parseUserInput(destination) + + numContainers := 0 + if len(sourceContainer) > 0 { + numContainers++ + } + if len(destContainer) > 0 { + numContainers++ + } + + if numContainers != 1 { + return "", "", "", "", errors.Errorf("invalid arguments %q, %q: exactly 1 container expected but %d specified", source, destination, numContainers) + } + + if len(sourcePath) == 0 || len(destPath) == 0 { + return "", "", "", "", errors.Errorf("invalid arguments %q, %q: you must specify paths", source, destination) + } + + return sourceContainer, sourcePath, destContainer, destPath, nil +} + +// parseUserInput parses the input string and returns, if specified, the name +// or ID of the container and the path. The input format is described in +// podman-cp(1) as "[nameOrID:]path". Colons in paths are supported as long +// they start with a dot or slash. +func parseUserInput(input string) (container string, path string) { + if len(input) == 0 { + return + } + path = input + + // If the input starts with a dot or slash, it cannot refer to a + // container. + if input[0] == '.' || input[0] == '/' { + return + } + + if spl := strings.SplitN(path, ":", 2); len(spl) == 2 { + container = spl[0] + path = spl[1] + } + return +} diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index bde2b6ef2..4af013134 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -19,6 +19,7 @@ type ServiceOptions struct { type SystemPruneOptions struct { All bool Volume bool + Filter []string } // SystemPruneReport provides report after system prune is executed. diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go index 9409df743..362053cce 100644 --- a/pkg/domain/infra/abi/cp.go +++ b/pkg/domain/infra/abi/cp.go @@ -2,46 +2,53 @@ package abi import ( "context" - "strings" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/pkg/copy" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" ) func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error { - srcCtr, srcPath := parsePath(ic.Libpod, source) - destCtr, destPath := parsePath(ic.Libpod, dest) - - if srcCtr != nil && destCtr != nil { - return errors.Errorf("invalid arguments %q, %q: you must use just one container", source, dest) + // Parse user input. + sourceContainerStr, sourcePath, destContainerStr, destPath, err := copy.ParseSourceAndDestination(source, dest) + if err != nil { + return err } - if srcCtr == nil && destCtr == nil { - return errors.Errorf("invalid arguments %q, %q: you must specify one container", source, dest) + + // Look up containers. + var sourceContainer, destContainer *libpod.Container + if len(sourceContainerStr) > 0 { + sourceContainer, err = ic.Libpod.LookupContainer(sourceContainerStr) + if err != nil { + return err + } } - if len(srcPath) == 0 || len(destPath) == 0 { - return errors.Errorf("invalid arguments %q, %q: you must specify paths", source, dest) + if len(destContainerStr) > 0 { + destContainer, err = ic.Libpod.LookupContainer(destContainerStr) + if err != nil { + return err + } } var sourceItem, destinationItem copy.CopyItem - var err error - // Copy from the container to the host. - if srcCtr != nil { - sourceItem, err = copy.CopyItemForContainer(srcCtr, srcPath, options.Pause, true) + + // Source ... container OR host. + if sourceContainer != nil { + sourceItem, err = copy.CopyItemForContainer(sourceContainer, sourcePath, options.Pause, true) defer sourceItem.CleanUp() if err != nil { return err } } else { - sourceItem, err = copy.CopyItemForHost(srcPath, true) + sourceItem, err = copy.CopyItemForHost(sourcePath, true) if err != nil { return err } } - if destCtr != nil { - destinationItem, err = copy.CopyItemForContainer(destCtr, destPath, options.Pause, false) + // Destination ... container OR host. + if destContainer != nil { + destinationItem, err = copy.CopyItemForContainer(destContainer, destPath, options.Pause, false) defer destinationItem.CleanUp() if err != nil { return err @@ -55,22 +62,9 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, } // Copy from the host to the container. - return copy.Copy(&sourceItem, &destinationItem, options.Extract) -} - -func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) { - if len(path) == 0 { - return nil, "" - } - if path[0] == '.' || path[0] == '/' { // A path cannot point to a container. - return nil, path - } - pathArr := strings.SplitN(path, ":", 2) - if len(pathArr) == 2 { - ctr, err := runtime.LookupContainer(pathArr[0]) - if err == nil { - return ctr, pathArr[1] - } + copier, err := copy.GetCopier(&sourceItem, &destinationItem, options.Extract) + if err != nil { + return err } - return nil, path + return copier.Copy() } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index ad7128b42..600d64b1d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -54,7 +54,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte } return buf, nil // no return if local image is not a list of images type - // continue on getting valid manifest through remote serice + // continue on getting valid manifest through remote service } else if errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { return nil, errors.Wrapf(err, "loading manifest %q", name) } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 3aeb6a2ee..5b983a3f4 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -212,8 +212,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY return nil, errors.Wrapf(err, "Failed to parse image %q", container.Image) } // In kube, if the image is tagged with latest, it should always pull + // but if the domain is localhost, that means the image was built locally + // so do not attempt a pull. if tagged, isTagged := named.(reference.NamedTagged); isTagged { - if tagged.Tag() == image.LatestTag { + if tagged.Tag() == image.LatestTag && reference.Domain(named) != image.DefaultLocalRegistry { pullPolicy = util.PullImageAlways } } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 7ed58092b..d6881fdc4 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "net/url" "os" "os/exec" "path/filepath" @@ -179,7 +180,16 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys found = true } systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...) - containerPruneReport, err := ic.pruneContainersHelper(nil) + containerPruneOptions := entities.ContainerPruneOptions{} + for _, f := range options.Filter { + t := strings.SplitN(f, "=", 2) + containerPruneOptions.Filters = make(url.Values) + if len(t) < 2 { + return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + containerPruneOptions.Filters.Add(t[0], t[1]) + } + containerPruneReport, err := ic.ContainerPrune(ctx, containerPruneOptions) if err != nil { return nil, err } @@ -194,7 +204,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys } } - results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, nil) + results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, options.Filter) if err != nil { return nil, err diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 3584668c7..e65fef0a4 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -732,7 +732,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o } func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error { - return errors.New("not implemented") + return nil + // return containers.Copy(ic.ClientCxt, source, dest, options) } // Shutdown Libpod engine diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index 1808f99b8..e0b039fb7 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -167,22 +167,23 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask g.AddLinuxMaskedPaths(mp) } } + for _, rp := range []string{ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger", + } { + if !util.StringInSlice(rp, unmask) { + g.AddLinuxReadonlyPaths(rp) + } + } } if pidModeIsHost && rootless.IsRootless() { return } - - for _, rp := range []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - } { - g.AddLinuxReadonlyPaths(rp) - } } // mask the paths provided by the user diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index 2ef0f4c23..bb8edabb7 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -103,7 +103,7 @@ func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) { } else if volumeSource.PersistentVolumeClaim != nil { return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) } else { - return nil, errors.Errorf("HostPath and PersistentVolumeClaim are currently the conly supported VolumeSource") + return nil, errors.Errorf("HostPath and PersistentVolumeClaim are currently the only supported VolumeSource") } } diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index dee140282..56947ff24 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -141,7 +141,7 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, configSpec.Process.Capabilities.Effective = caplist configSpec.Process.Capabilities.Permitted = caplist } else { - userCaps, err := capabilities.NormalizeCapabilities(s.CapAdd) + userCaps, err := capabilities.MergeCapabilities(nil, s.CapAdd, nil) if err != nil { return errors.Wrapf(err, "capabilities requested by user are not valid: %q", strings.Join(s.CapAdd, ",")) } |