diff options
Diffstat (limited to 'pkg')
31 files changed, 686 insertions, 107 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 409a74de2..6e85872b2 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -66,7 +66,20 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return } - sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS) + + imgNameOrID := newImage.ID() + // if the img had multi names with the same sha256 ID, should use the InputName, not the ID + if len(newImage.Names()) > 1 { + imageRef, err := utils.ParseDockerReference(newImage.InputName) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + return + } + // maybe the InputName has no tag, so use full name to display + imgNameOrID = imageRef.DockerReference().String() + } + + sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS) if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen")) return 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/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index 71b848932..f76e18ee3 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -3,6 +3,7 @@ package compat import ( "encoding/json" "net/http" + "net/url" "time" "github.com/containers/podman/v2/libpod" @@ -254,14 +255,15 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - // TODO: We have no ability to pass pruning filters to `PruneVolumes()` so - // we'll explicitly reject the request if we see any - if len(query.Filters) > 0 { - utils.InternalServerError(w, errors.New("filters for pruning volumes is not implemented")) + + f := (url.Values)(query.Filters) + filterFuncs, err := filters.GenerateVolumeFilters(f) + if err != nil { + utils.Error(w, "Something when wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters for %s", f.Encode())) return } - pruned, err := runtime.PruneVolumes(r.Context()) + pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs) if err != nil { utils.InternalServerError(w, err) 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/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index b0d40fd8b..b02a6a8ce 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -3,6 +3,7 @@ package libpod import ( "encoding/json" "net/http" + "net/url" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" @@ -180,8 +181,25 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { func pruneVolumesHelper(r *http.Request) ([]*entities.VolumePruneReport, error) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) ) - pruned, err := runtime.PruneVolumes(r.Context()) + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + return nil, err + } + + f := (url.Values)(query.Filters) + filterFuncs, err := filters.GenerateVolumeFilters(f) + if err != nil { + return nil, err + } + + pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs) if err != nil { return nil, err } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index f2cb3147c..7b26037eb 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -182,30 +182,65 @@ func pingNewConnection(ctx context.Context) error { func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) { // if you modify the authmethods or their conditionals, you will also need to make similar // changes in the client (currently cmd/podman/system/connection/add getUDS). - authMethods := []ssh.AuthMethod{} + + var signers []ssh.Signer // order Signers are appended to this list determines which key is presented to server + if len(identity) > 0 { - auth, err := terminal.PublicKey(identity, []byte(passPhrase)) + s, err := terminal.PublicKey(identity, []byte(passPhrase)) if err != nil { return Connection{}, errors.Wrapf(err, "failed to parse identity %q", identity) } - logrus.Debugf("public key signer enabled for identity %q", identity) - authMethods = append(authMethods, auth) + + signers = append(signers, s) + logrus.Debugf("SSH Ident Key %q %s %s", identity, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) } if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { - logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer(s) enabled", sock) c, err := net.Dial("unix", sock) if err != nil { return Connection{}, err } - a := agent.NewClient(c) - authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) + + agentSigners, err := agent.NewClient(c).Signers() + if err != nil { + return Connection{}, err + } + signers = append(signers, agentSigners...) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + for _, s := range agentSigners { + logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + } + } + + var authMethods []ssh.AuthMethod + if len(signers) > 0 { + var dedup = make(map[string]ssh.Signer) + // Dedup signers based on fingerprint, ssh-agent keys override CONTAINER_SSHKEY + for _, s := range signers { + fp := ssh.FingerprintSHA256(s.PublicKey()) + if _, found := dedup[fp]; found { + logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + dedup[fp] = s + } + + var uniq []ssh.Signer + for _, s := range dedup { + uniq = append(uniq, s) + } + authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { + return uniq, nil + })) } if pw, found := _url.User.Password(); found { authMethods = append(authMethods, ssh.Password(pw)) } + if len(authMethods) == 0 { callback := func() (string, error) { pass, err := terminal.ReadPassword("Login password:") 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/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/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index dc90d4d00..861a02441 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -144,16 +144,15 @@ var _ = Describe("Podman volumes", func() { Expect(vols[0].Name).To(Equal("homer")) }) - // TODO we need to add filtering to tests It("prune unused volume", func() { // Pruning when no volumes present should be ok - _, err := volumes.Prune(connText) + _, err := volumes.Prune(connText, nil) Expect(err).To(BeNil()) // Removing an unused volume should work _, err = volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) - vols, err := volumes.Prune(connText) + vols, err := volumes.Prune(connText, nil) Expect(err).To(BeNil()) Expect(len(vols)).To(BeNumerically("==", 1)) @@ -163,11 +162,45 @@ var _ = Describe("Podman volumes", func() { Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"}) session.Wait(45) - vols, err = volumes.Prune(connText) + vols, err = volumes.Prune(connText, nil) Expect(err).To(BeNil()) Expect(len(vols)).To(BeNumerically("==", 1)) _, err = volumes.Inspect(connText, "homer") Expect(err).To(BeNil()) + + // Removing volume with non matching filter shouldn't prune any volumes + filters := make(map[string][]string) + filters["label"] = []string{"label1=idontmatch"} + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value1", + }}) + Expect(err).To(BeNil()) + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 0)) + vol2, err := volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value2", + }}) + Expect(err).To(BeNil()) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value3", + }}) + Expect(err).To(BeNil()) + + // Removing volume with matching filter label and value should remove specific entry + filters = make(map[string][]string) + filters["label"] = []string{"label1=value2"} + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 1)) + Expect(vols[0].Id).To(Equal(vol2.Name)) + + // Removing volumes with matching filter label should remove all matching volumes + filters = make(map[string][]string) + filters["label"] = []string{"label1"} + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 2)) }) }) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 00f1e5720..b1be257b8 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -75,7 +75,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL } // Prune removes unused volumes from the local filesystem. -func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) { +func Prune(ctx context.Context, filters map[string][]string) ([]*entities.VolumePruneReport, error) { var ( pruned []*entities.VolumePruneReport ) @@ -83,7 +83,15 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil) + params := url.Values{} + if len(filters) > 0 { + strFilters, err := bindings.FiltersToString(filters) + if err != nil { + return nil, err + } + params.Set("filters", strFilters) + } + response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil) if err != nil { return nil, err } diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go index b4e8446cb..ff3b087ed 100644 --- a/pkg/domain/entities/container_ps.go +++ b/pkg/domain/entities/container_ps.go @@ -12,6 +12,8 @@ import ( // Listcontainer describes a container suitable for listing type ListContainer struct { + // AutoRemove + AutoRemove bool // Container command Command []string // Container creation time diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 39d679eaf..01086a2b3 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -227,8 +227,10 @@ type ContainerLogsOptions struct { Tail int64 // Show timestamps in the logs. Timestamps bool - // Write the logs to Writer. - Writer io.Writer + // Write the stdout to this Writer. + StdoutWriter io.Writer + // Write the stderr to this Writer. + StderrWriter io.Writer } // ExecOptions describes the cli values to exec into diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 5ad475133..ac9073402 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -80,6 +80,6 @@ type ContainerEngine interface { VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) - VolumePrune(ctx context.Context) ([]*VolumePruneReport, error) + VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*VolumePruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 81f12bff7..1538cbb8b 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -344,6 +344,7 @@ type SignOptions struct { Directory string SignBy string CertDir string + All bool } // SignReport describes the result of signing 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/entities/volumes.go b/pkg/domain/entities/volumes.go index 1bc1e4301..e6b29e374 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -1,6 +1,7 @@ package entities import ( + "net/url" "time" docker_api_types "github.com/docker/docker/api/types" @@ -109,6 +110,12 @@ type VolumeInspectReport struct { *VolumeConfigResponse } +// VolumePruneOptions describes the options needed +// to prune a volume from the CLI +type VolumePruneOptions struct { + Filters url.Values `json:"filters" schema:"filters"` +} + type VolumePruneReport struct { Err error Id string //nolint diff --git a/pkg/domain/filters/helpers.go b/pkg/domain/filters/helpers.go new file mode 100644 index 000000000..6a5fb68b1 --- /dev/null +++ b/pkg/domain/filters/helpers.go @@ -0,0 +1,20 @@ +package filters + +import ( + "net/url" + "strings" + + "github.com/pkg/errors" +) + +func ParseFilterArgumentsIntoFilters(filters []string) (url.Values, error) { + parsedFilters := make(url.Values) + for _, f := range filters { + t := strings.SplitN(f, "=", 2) + if len(t) < 2 { + return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + parsedFilters.Add(t[0], t[1]) + } + return parsedFilters, nil +} diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 7819d3cdf..69bef4961 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -1,13 +1,14 @@ package filters import ( + "net/url" "strings" "github.com/containers/podman/v2/libpod" "github.com/pkg/errors" ) -func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) { +func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { var vf []libpod.VolumeFilter for filter, v := range filters { for _, val := range v { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index ff4277a2e..ec65dbe44 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -925,7 +925,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { - if options.Writer == nil { + if options.StdoutWriter == nil && options.StderrWriter == nil { return errors.New("no io.Writer set for container logs") } @@ -963,7 +963,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin }() for line := range logChannel { - fmt.Fprintln(options.Writer, line.String(logOpts)) + line.Write(options.StdoutWriter, options.StderrWriter, logOpts) } return nil diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 57a2bc4cf..394ba359c 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -28,6 +28,8 @@ import ( "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" + dockerRef "github.com/docker/distribution/reference" + "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -718,9 +720,9 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie logrus.Errorf("unable to close %s image source %q", srcRef.DockerReference().Name(), err) } }() - getManifest, _, err := rawSource.GetManifest(ctx, nil) + topManifestBlob, manifestType, err := rawSource.GetManifest(ctx, nil) if err != nil { - return errors.Wrapf(err, "error getting getManifest") + return errors.Wrapf(err, "error getting manifest blob") } dockerReference := rawSource.Reference().DockerReference() if dockerReference == nil { @@ -743,34 +745,34 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie return err } } - manifestDigest, err := manifest.Digest(getManifest) + manifestDigest, err := manifest.Digest(topManifestBlob) if err != nil { return err } - // create signature - newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy) - if err != nil { - return errors.Wrapf(err, "error creating new signature") - } - // create the signstore file - signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, manifestDigest.Algorithm(), manifestDigest.Hex()) - if err := os.MkdirAll(signatureDir, 0751); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - logrus.Error(err) - return nil + if options.All { + if !manifest.MIMETypeIsMultiImage(manifestType) { + return errors.Errorf("%s is not a multi-architecture image (manifest type %s)", signimage, manifestType) + } + list, err := manifest.ListFromBlob(topManifestBlob, manifestType) + if err != nil { + return errors.Wrapf(err, "Error parsing manifest list %q", string(topManifestBlob)) + } + instanceDigests := list.Instances() + for _, instanceDigest := range instanceDigests { + digest := instanceDigest + man, _, err := rawSource.GetManifest(ctx, &digest) + if err != nil { + return err + } + if err = putSignature(man, mech, sigStoreDir, instanceDigest, dockerReference, options); err != nil { + return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), instanceDigest) + } } - } - sigFilename, err := getSigFilename(signatureDir) - if err != nil { - logrus.Errorf("error creating sigstore file: %v", err) return nil } - err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) - if err != nil { - logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) - return nil + if err = putSignature(topManifestBlob, mech, sigStoreDir, manifestDigest, dockerReference, options); err != nil { + return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), manifestDigest) } return nil }() @@ -806,3 +808,26 @@ func localPathFromURI(url *url.URL) (string, error) { } return url.Path, nil } + +// putSignature creates signature and saves it to the signstore file +func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error { + newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy) + if err != nil { + return err + } + signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex()) + if err := os.MkdirAll(signatureDir, 0751); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return err + } + } + sigFilename, err := getSigFilename(signatureDir) + if err != nil { + return err + } + if err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644); err != nil { + return err + } + return nil +} diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 7ed58092b..b6da364fc 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 @@ -214,7 +224,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...) } if options.Volume { - volumePruneReport, err := ic.pruneVolumesHelper(ctx) + volumePruneReport, err := ic.pruneVolumesHelper(ctx, nil) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index a7262f61b..515e52754 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -127,12 +127,16 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin return reports, errs, nil } -func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { - return ic.pruneVolumesHelper(ctx) +func (ic *ContainerEngine) VolumePrune(ctx context.Context, options entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { + filterFuncs, err := filters.GenerateVolumeFilters(options.Filters) + if err != nil { + return nil, err + } + return ic.pruneVolumesHelper(ctx, filterFuncs) } -func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) { - pruned, err := ic.Libpod.PruneVolumes(ctx) +func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context, filterFuncs []libpod.VolumeFilter) ([]*entities.VolumePruneReport, error) { + pruned, err := ic.Libpod.PruneVolumes(ctx, filterFuncs) if err != nil { return nil, err } diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go index 6c85e837e..3fddf577c 100644 --- a/pkg/domain/infra/runtime_tunnel.go +++ b/pkg/domain/infra/runtime_tunnel.go @@ -5,18 +5,38 @@ package infra import ( "context" "fmt" + "sync" "github.com/containers/podman/v2/pkg/bindings" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra/tunnel" ) +var ( + connectionMutex = &sync.Mutex{} + connection *context.Context +) + +func newConnection(uri string, identity string) (context.Context, error) { + connectionMutex.Lock() + defer connectionMutex.Unlock() + + if connection == nil { + ctx, err := bindings.NewConnectionWithIdentity(context.Background(), uri, identity) + if err != nil { + return ctx, err + } + connection = &ctx + } + return *connection, nil +} + func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) { switch facts.EngineMode { case entities.ABIMode: return nil, fmt.Errorf("direct runtime not supported") case entities.TunnelMode: - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity) + ctx, err := newConnection(facts.URI, facts.Identity) return &tunnel.ContainerEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) @@ -28,7 +48,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) case entities.ABIMode: return nil, fmt.Errorf("direct image runtime not supported") case entities.TunnelMode: - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity) + ctx, err := newConnection(facts.URI, facts.Identity) return &tunnel.ImageEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index e65fef0a4..ad7688f62 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -360,11 +360,12 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, options entities.ContainerLogsOptions) error { since := options.Since.Format(time.RFC3339) tail := strconv.FormatInt(options.Tail, 10) - stdout := options.Writer != nil + stdout := options.StdoutWriter != nil + stderr := options.StderrWriter != nil opts := containers.LogOptions{ Follow: &options.Follow, Since: &since, - Stderr: &stdout, + Stderr: &stderr, Stdout: &stdout, Tail: &tail, Timestamps: &options.Timestamps, @@ -372,10 +373,11 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, } var err error - outCh := make(chan string) + stdoutCh := make(chan string) + stderrCh := make(chan string) ctx, cancel := context.WithCancel(context.Background()) go func() { - err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, outCh, outCh) + err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, stdoutCh, stderrCh) cancel() }() @@ -383,8 +385,14 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, select { case <-ctx.Done(): return err - case line := <-outCh: - _, _ = io.WriteString(options.Writer, line+"\n") + case line := <-stdoutCh: + if options.StdoutWriter != nil { + _, _ = io.WriteString(options.StdoutWriter, line+"\n") + } + case line := <-stderrCh: + if options.StderrWriter != nil { + _, _ = io.WriteString(options.StderrWriter, line+"\n") + } } } } @@ -515,6 +523,29 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri reports = append(reports, &report) return reports, errors.Wrapf(report.Err, "unable to start container %s", name) } + if ctr.AutoRemove { + // Defer the removal, so we can return early if needed and + // de-spaghetti the code. + defer func() { + shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, ctr.ID) + if err != nil { + logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err) + return + } + + if !shouldRestart { + if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Warnf("Container %s does not exist: %v", ctr.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", ctr.ID, err) + } + } + } + }() + } + exitCode, err := containers.Wait(ic.ClientCxt, name, nil) if err == define.ErrNoSuchCtr { // Check events @@ -535,6 +566,16 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if !ctrRunning { err = containers.Start(ic.ClientCxt, name, &options.DetachKeys) if err != nil { + if ctr.AutoRemove { + if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Warnf("Container %s does not exist: %v", ctr.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", ctr.ID, err) + } + } + } report.Err = errors.Wrapf(err, "unable to start container %q", name) report.ExitCode = define.ExitCode(err) reports = append(reports, &report) diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index c0df2bb7b..b431fc8bd 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -68,8 +68,8 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin return reports, errs, nil } -func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { - return volumes.Prune(ic.ClientCxt) +func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { + return volumes.Prune(ic.ClientCxt, (map[string][]string)(opts.Filters)) } func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index cfdf3ee49..6c26e8708 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -179,24 +179,25 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities } ps := entities.ListContainer{ - Command: conConfig.Command, - Created: conConfig.CreatedTime, - Exited: exited, - ExitCode: exitCode, - ExitedAt: exitedTime.Unix(), - ID: conConfig.ID, - Image: conConfig.RootfsImageName, - ImageID: conConfig.RootfsImageID, - IsInfra: conConfig.IsInfra, - Labels: conConfig.Labels, - Mounts: ctr.UserVolumes(), - Names: []string{conConfig.Name}, - Pid: pid, - Pod: conConfig.Pod, - Ports: portMappings, - Size: size, - StartedAt: startedTime.Unix(), - State: conState.String(), + AutoRemove: ctr.AutoRemove(), + Command: conConfig.Command, + Created: conConfig.CreatedTime, + Exited: exited, + ExitCode: exitCode, + ExitedAt: exitedTime.Unix(), + ID: conConfig.ID, + Image: conConfig.RootfsImageName, + ImageID: conConfig.RootfsImageID, + IsInfra: conConfig.IsInfra, + Labels: conConfig.Labels, + Mounts: ctr.UserVolumes(), + Names: []string{conConfig.Name}, + Pid: pid, + Pod: conConfig.Pod, + Ports: portMappings, + Size: size, + StartedAt: startedTime.Unix(), + State: conState.String(), } if opts.Pod && len(conConfig.Pod) > 0 { podName, err := rt.GetName(conConfig.Pod) diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go index 169bec2af..231b47974 100644 --- a/pkg/terminal/util.go +++ b/pkg/terminal/util.go @@ -61,7 +61,7 @@ func ReadPassword(prompt string) (pw []byte, err error) { } } -func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) { +func PublicKey(path string, passphrase []byte) (ssh.Signer, error) { key, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -75,12 +75,9 @@ func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) { if len(passphrase) == 0 { passphrase = ReadPassphrase() } - signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase) - if err != nil { - return nil, err - } + return ssh.ParsePrivateKeyWithPassphrase(key, passphrase) } - return ssh.PublicKeys(signer), nil + return signer, nil } func ReadPassphrase() []byte { |