diff options
26 files changed, 583 insertions, 232 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 6fa2b3c71..4eeb09d42 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -396,6 +396,10 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "Assign a name to the container", ) createFlags.Bool( + "no-healthcheck", false, + "Disable healthchecks on container", + ) + createFlags.Bool( "oom-kill-disable", false, "Disable OOM Killer", ) diff --git a/cmd/podman/port.go b/cmd/podman/port.go index eef3d4b1d..4bb79a3a2 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" + "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -16,7 +17,7 @@ var ( portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT ` _portCommand = &cobra.Command{ - Use: "port [flags] CONTAINER", + Use: "port [flags] CONTAINER [PORT]", Short: "List port mappings or a specific mapping for the container", Long: portDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -48,8 +49,8 @@ func init() { func portCmd(c *cliconfig.PortValues) error { var ( - userProto string - userPort int + userPort nat.Port + err error ) args := c.InputArgs @@ -70,25 +71,19 @@ func portCmd(c *cliconfig.PortValues) error { if len(args) == 1 && c.Latest { port = args[0] } - if port != "" { + if len(port) > 0 { fields := strings.Split(port, "/") - // User supplied at least port - var err error - // User supplied port and protocol - if len(fields) == 2 { - userProto = fields[1] - } - if len(fields) >= 1 { - p := fields[0] - userPort, err = strconv.Atoi(p) - if err != nil { - return errors.Wrapf(err, "unable to format port") - } - } - // Format is incorrect if len(fields) > 2 || len(fields) < 1 { return errors.Errorf("port formats are port/protocol. '%s' is invalid", port) } + if len(fields) == 1 { + fields = append(fields, "tcp") + } + + userPort, err = nat.NewPort(fields[1], fields[0]) + if err != nil { + return err + } } runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) @@ -96,7 +91,6 @@ func portCmd(c *cliconfig.PortValues) error { return errors.Wrapf(err, "could not get runtime") } defer runtime.DeferredShutdown(false) - containers, err := runtime.Port(c) if err != nil { return err @@ -122,17 +116,18 @@ func portCmd(c *cliconfig.PortValues) error { fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) continue } - // We have a match on ports - if v.ContainerPort == int32(userPort) { - if userProto == "" || userProto == v.Protocol { - fmt.Printf("%s:%d\n", hostIP, v.HostPort) - found = true - break - } + containerPort, err := nat.NewPort(v.Protocol, strconv.Itoa(int(v.ContainerPort))) + if err != nil { + return err + } + if containerPort == userPort { + fmt.Printf("%s:%d\n", hostIP, v.HostPort) + found = true + break } } if !found && port != "" { - return errors.Errorf("failed to find published port '%d'", userPort) + return errors.Errorf("failed to find published port %q", port) } } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 5b244699c..0814eeba3 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -120,12 +120,13 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod. imageName = newImage.ID() } - // if the user disabled the healthcheck with "none", we skip adding it + // if the user disabled the healthcheck with "none" or the no-healthcheck + // options is provided, we skip adding it healthCheckCommandInput := c.String("healthcheck-command") // the user didn't disable the healthcheck but did pass in a healthcheck command // now we need to make a healthcheck from the commandline input - if healthCheckCommandInput != "none" { + if healthCheckCommandInput != "none" && !c.Bool("no-healthcheck") { if len(healthCheckCommandInput) > 0 { healthCheck, err = makeHealthCheckFromCli(c) if err != nil { diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go index ee212234f..e76750042 100644 --- a/cmd/podman/shared/intermediate.go +++ b/cmd/podman/shared/intermediate.go @@ -425,6 +425,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes m["memory-swappiness"] = newCRInt64(c, "memory-swappiness") m["name"] = newCRString(c, "name") m["network"] = newCRString(c, "network") + m["no-healthcheck"] = newCRBool(c, "no-healthcheck") m["no-hosts"] = newCRBool(c, "no-hosts") m["oom-kill-disable"] = newCRBool(c, "oom-kill-disable") m["oom-score-adj"] = newCRInt(c, "oom-score-adj") diff --git a/completions/bash/podman b/completions/bash/podman index 958633bf0..13be64e06 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1888,6 +1888,11 @@ _podman_container_run() { --expose --gidmap --group-add + --health-cmd + --health-interval + --health-retries + --health-start-period + --health-timeout --hostname -h --http-proxy --image-volume @@ -1906,6 +1911,7 @@ _podman_container_run() { --memory-reservation --name --network + --no-healthcheck --no-hosts --oom-score-adj --pid diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 738644c16..12f099e65 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -172,13 +172,20 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. -**--device**=*device* +**--device**=_host-device_[**:**_container-device_][**:**_permissions_] -Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm) +Add a host device to the container. Optional *permissions* parameter +can be used to specify device permissions, it is combination of +**r** for read, **w** for write, and **m** for **mknod**(2). -Note: if the user only has access rights via a group then accessing the device -from inside a rootless container will fail. The `crun` runtime offers a -workaround for this by adding the option `--annotation run.oci.keep_original_groups=1`. +Example: **--device=/dev/sdc:/dev/xvdc:rwm**. + +Note: if _host_device_ is a symbolic link then it will be resolved first. +The container will only store the major and minor numbers of the host device. + +Note: if the user only has access rights via a group, accessing the device +from inside a rootless container will fail. The **crun**(1) runtime offers a +workaround for this by adding the option **--annotation run.oci.keep_original_groups=1**. **--disable-compression, -D** diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index ca38be6a1..3c5f81764 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -201,13 +201,20 @@ it in the **libpod.conf** file: see **libpod.conf(5)** for more information. Specify the key sequence for detaching a container. Format is a single character `[a-Z]` or one or more `ctrl-<value>` characters where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. Specifying "" will disable this feature. The default is *ctrl-p,ctrl-q*. -**--device**=*device* +**--device**=_host-device_[**:**_container-device_][**:**_permissions_] -Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm) +Add a host device to the container. Optional *permissions* parameter +can be used to specify device permissions, it is combination of +**r** for read, **w** for write, and **m** for **mknod**(2). -Note: if the user only has access rights via a group then accessing the device -from inside a rootless container will fail. The `crun` runtime offers a -workaround for this by adding the option `--annotation run.oci.keep_original_groups=1`. +Example: **--device=/dev/sdc:/dev/xvdc:rwm**. + +Note: if _host_device_ is a symbolic link then it will be resolved first. +The container will only store the major and minor numbers of the host device. + +Note: if the user only has access rights via a group, accessing the device +from inside a rootless container will fail. The **crun**(1) runtime offers a +workaround for this by adding the option **--annotation run.oci.keep_original_groups=1**. **--device-cgroup-rule**="type major:minor mode" @@ -557,6 +564,10 @@ Valid values are: Not implemented +**--no-healthcheck**=*true|false* + +Disable any defined healthchecks for container. + **--no-hosts**=*true|false* Do not create /etc/hosts for the container. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index f391307b3..220b32a46 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -218,6 +218,9 @@ can be used to specify device permissions, it is combination of Example: **--device=/dev/sdc:/dev/xvdc:rwm**. +Note: if _host_device_ is a symbolic link then it will be resolved first. +The container will only store the major and minor numbers of the host device. + Note: if the user only has access rights via a group, accessing the device from inside a rootless container will fail. The **crun**(1) runtime offers a workaround for this by adding the option **--annotation run.oci.keep_original_groups=1**. @@ -560,6 +563,10 @@ Valid _mode_ values are: Not implemented. +**--no-healthcheck**=*true|false* + +Disable any defined healthchecks for container. + **--no-hosts**=**true**|**false** Do not create _/etc/hosts_ for the container. @@ -42,7 +42,7 @@ require ( github.com/opencontainers/runc v1.0.0-rc9 github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 github.com/opencontainers/runtime-tools v0.9.0 - github.com/opencontainers/selinux v1.3.1 + github.com/opencontainers/selinux v1.3.2 github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 @@ -383,6 +383,8 @@ github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOl github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opencontainers/selinux v1.3.1 h1:dn2Rc3wTEvTB6iVqoFrKKeMb0uZ38ZheeyMu2h5C1TI= github.com/opencontainers/selinux v1.3.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= +github.com/opencontainers/selinux v1.3.2 h1:DR4lL9SYVjgcTZKEZIncvDU06fKSc/eygjmNGOA3E1s= +github.com/opencontainers/selinux v1.3.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316 h1:enQG2QUGwug4fR1yM6hL0Fjzx6Km/exZY6RbSPwMu3o= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316/go.mod h1:dv+J0b/HWai0QnMVb37/H0v36klkLBi2TNpPeWDxX10= github.com/openshift/imagebuilder v1.1.1 h1:KAUR31p8UBJdfVO42azWgb+LeMAed2zaKQ19e0C0X2I= diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index ab4255f89..78057e3f9 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -1116,7 +1116,11 @@ func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) { ) if !c.All { - containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime) + names := []string{} + if len(c.InputArgs) >= 1 { + names = []string{c.InputArgs[0]} + } + containers, err = shortcuts.GetContainersByContext(false, c.Latest, names, r.Runtime) } else { containers, err = r.Runtime.GetRunningContainers() } diff --git a/pkg/api/handlers/images.go b/pkg/api/handlers/images.go index 96bcbdc96..2086ce748 100644 --- a/pkg/api/handlers/images.go +++ b/pkg/api/handlers/images.go @@ -7,6 +7,7 @@ import ( "os" "strconv" + "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/api/handlers/utils" @@ -147,10 +148,36 @@ func SearchImages(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 filters are a bit undefined here in terms of what exactly the input looks - // like. We need to understand that a bit more. + + filter := image.SearchFilter{} + if len(query.Filters) > 0 { + if len(query.Filters["stars"]) > 0 { + stars, err := strconv.Atoi(query.Filters["stars"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.Stars = stars + } + if len(query.Filters["is-official"]) > 0 { + isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.IsOfficial = types.NewOptionalBool(isOfficial) + } + if len(query.Filters["is-automated"]) > 0 { + isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + filter.IsAutomated = types.NewOptionalBool(isAutomated) + } + } options := image.SearchOptions{ - Filter: image.SearchFilter{}, + Filter: filter, Limit: query.Limit, } results, err := image.SearchImages(query.Term, options) diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index eac0e4dad..71603e6cc 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "strconv" + "strings" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" @@ -133,11 +134,16 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { var libpodFilters = []string{} if _, found := r.URL.Query()["filters"]; found { - all, err = strconv.ParseBool(query.Filters["all"][0]) - if err != nil { - utils.InternalServerError(w, err) - return + dangling := query.Filters["all"] + if len(dangling) > 0 { + all, err = strconv.ParseBool(query.Filters["all"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } } + // dangling is special and not implemented in the libpod side of things + delete(query.Filters, "dangling") for k, v := range query.Filters { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } @@ -157,7 +163,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { Compress bool `schema:"compress"` Format string `schema:"format"` }{ - // override any golang type defaults + Format: "docker-archive", } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -166,11 +172,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } - if len(query.Format) < 1 { - utils.InternalServerError(w, errors.New("format parameter cannot be empty.")) - return - } - tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) @@ -186,6 +187,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.ImageNotFound(w, name, err) return } + if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return @@ -234,8 +236,20 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) return } - - utils.WriteResponse(w, http.StatusOK, []handlers.LibpodImagesLoadReport{{ID: loadedImage}}) + split := strings.Split(loadedImage, ",") + newImage, err := runtime.ImageRuntime().NewFromLocal(split[0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + // TODO this should go into libpod proper at some point. + if len(query.Reference) > 0 { + if err := newImage.TagImage(query.Reference); err != nil { + utils.InternalServerError(w, err) + return + } + } + utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesLoadReport{ID: loadedImage}) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -275,7 +289,6 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { tmpfile.Close() source = tmpfile.Name() } - importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image")) diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 75f1fc6a5..ba5f9c3aa 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -206,7 +206,7 @@ func unixClient(_url *url.URL) (*http.Client, error) { } // DoRequest assembles the http request and returns the response -func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams map[string]string, pathValues ...string) (*APIResponse, error) { +func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, pathValues ...string) (*APIResponse, error) { var ( err error response *http.Response @@ -225,12 +225,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, return nil, err } if len(queryParams) > 0 { - // if more desirable we could use url to form the encoded endpoint with params - r := req.URL.Query() - for k, v := range queryParams { - r.Add(k, v) - } - req.URL.RawQuery = r.Encode() + req.URL.RawQuery = queryParams.Encode() } // Give the Do three chances in the case of a comm/service hiccup for i := 0; i < 3; i++ { diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index a437e9a9b..2985787a6 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -3,6 +3,7 @@ package containers import ( "context" "net/http" + "net/url" "strconv" "github.com/containers/libpod/libpod" @@ -21,28 +22,28 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int return nil, err } var containers []lpapiv2.ListContainer - params := make(map[string]string) + params := url.Values{} if all != nil { - params["all"] = strconv.FormatBool(*all) + params.Set("all", strconv.FormatBool(*all)) } if last != nil { - params["last"] = strconv.Itoa(*last) + params.Set("last", strconv.Itoa(*last)) } if pod != nil { - params["pod"] = strconv.FormatBool(*pod) + params.Set("pod", strconv.FormatBool(*pod)) } if size != nil { - params["size"] = strconv.FormatBool(*size) + params.Set("size", strconv.FormatBool(*size)) } if sync != nil { - params["sync"] = strconv.FormatBool(*sync) + params.Set("sync", strconv.FormatBool(*sync)) } if filters != nil { filterString, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = filterString + params.Set("filters", filterString) } response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params) if err != nil { @@ -63,13 +64,13 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if filters != nil { filterString, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = filterString + params.Set("filters", filterString) } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params) if err != nil { @@ -86,12 +87,12 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if force != nil { - params["force"] = strconv.FormatBool(*force) + params.Set("force", strconv.FormatBool(*force)) } if volumes != nil { - params["vols"] = strconv.FormatBool(*volumes) + params.Set("vols", strconv.FormatBool(*volumes)) } response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID) if err != nil { @@ -109,9 +110,9 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if size != nil { - params["size"] = strconv.FormatBool(*size) + params.Set("size", strconv.FormatBool(*size)) } response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID) if err != nil { @@ -129,8 +130,8 @@ func Kill(ctx context.Context, nameOrID string, signal string) error { if err != nil { return err } - params := make(map[string]string) - params["signal"] = signal + params := url.Values{} + params.Set("signal", signal) response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID) if err != nil { return err @@ -162,9 +163,9 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if timeout != nil { - params["t"] = strconv.Itoa(*timeout) + params.Set("t", strconv.Itoa(*timeout)) } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID) if err != nil { @@ -181,9 +182,9 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if detachKeys != nil { - params["detachKeys"] = *detachKeys + params.Set("detachKeys", *detachKeys) } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID) if err != nil { @@ -242,13 +243,13 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { // Stop stops a running container. The timeout is optional. The nameOrID can be a container name // or a partial/full ID func Stop(ctx context.Context, nameOrID string, timeout *int) error { - params := make(map[string]string) + params := url.Values{} conn, err := bindings.GetClient(ctx) if err != nil { return err } if timeout != nil { - params["t"] = strconv.Itoa(*timeout) + params.Set("t", strconv.Itoa(*timeout)) } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID) if err != nil { diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 271d58952..c84aa4601 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -2,8 +2,10 @@ package images import ( "context" + "errors" "io" "net/http" + "net/url" "strconv" "github.com/containers/libpod/pkg/api/handlers" @@ -33,16 +35,16 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handl if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if all != nil { - params["all"] = strconv.FormatBool(*all) + params.Set("all", strconv.FormatBool(*all)) } if filters != nil { strFilters, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = strFilters + params.Set("filters", strFilters) } response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params) if err != nil { @@ -58,9 +60,9 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageD if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if size != nil { - params["size"] = strconv.FormatBool(*size) + params.Set("size", strconv.FormatBool(*size)) } inspectedData := inspect.ImageData{} response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID) @@ -88,15 +90,21 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, return history, response.Process(&history) } -func Load(ctx context.Context, r io.Reader) error { +func Load(ctx context.Context, r io.Reader, name *string) (string, error) { + var id handlers.IDResponse conn, err := bindings.GetClient(ctx) if err != nil { - return err + return "", err } - // TODO this still needs error handling added - //_, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint - _ = conn - return bindings.ErrNotImplemented + params := url.Values{} + if name != nil { + params.Set("reference", *name) + } + response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params) + if err != nil { + return "", err + } + return id.ID, response.Process(&id) } // Remove deletes an image from local storage. The optional force parameter will forcibly remove @@ -107,9 +115,9 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]str if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if force != nil { - params["force"] = strconv.FormatBool(*force) + params.Set("force", strconv.FormatBool(*force)) } response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID) if err != nil { @@ -125,12 +133,12 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if format != nil { - params["format"] = *format + params.Set("format", *format) } if compress != nil { - params["compress"] = strconv.FormatBool(*compress) + params.Set("compress", strconv.FormatBool(*compress)) } response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID) if err != nil { @@ -153,13 +161,13 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = stringFilter + params.Set("filters", stringFilter) } response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params) if err != nil { @@ -174,9 +182,9 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error { if err != nil { return err } - params := make(map[string]string) - params["tag"] = tag - params["repo"] = repo + params := url.Values{} + params.Set("tag", tag) + params.Set("repo", repo) response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID) if err != nil { return err @@ -185,3 +193,35 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error { } func Build(nameOrId string) {} + +// Imports adds the given image to the local image store. This can be done by file and the given reader +// or via the url parameter. Additional metadata can be associated with the image by using the changes and +// message parameters. The image can also be tagged given a reference. One of url OR r must be provided. +func Import(ctx context.Context, changes []string, message, reference, u *string, r io.Reader) (string, error) { + var id handlers.IDResponse + if r != nil && u != nil { + return "", errors.New("url and r parameters cannot be used together") + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + params := url.Values{} + for _, change := range changes { + params.Add("changes", change) + } + if message != nil { + params.Set("message", *message) + } + if reference != nil { + params.Set("reference", *reference) + } + if u != nil { + params.Set("url", *u) + } + response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params) + if err != nil { + return "", err + } + return id.ID, response.Process(&id) +} diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go index dca1b0e63..183ff3d77 100644 --- a/pkg/bindings/images/search.go +++ b/pkg/bindings/images/search.go @@ -3,6 +3,7 @@ package images import ( "context" "net/http" + "net/url" "strconv" "github.com/containers/libpod/libpod/image" @@ -20,17 +21,17 @@ func Search(ctx context.Context, term string, limit *int, filters map[string][]s if err != nil { return nil, err } - params := make(map[string]string) - params["term"] = term + params := url.Values{} + params.Set("term", term) if limit != nil { - params["limit"] = strconv.Itoa(*limit) + params.Set("limit", strconv.Itoa(*limit)) } if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = stringFilter + params.Set("filters", stringFilter) } response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params) if err != nil { diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 838b22e43..1a8c31be1 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -3,6 +3,7 @@ package pods import ( "context" "net/http" + "net/url" "strconv" "github.com/containers/libpod/libpod" @@ -48,9 +49,9 @@ func Kill(ctx context.Context, nameOrID string, signal *string) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if signal != nil { - params["signal"] = *signal + params.Set("signal", *signal) } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID) if err != nil { @@ -95,13 +96,13 @@ func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspec if err != nil { return nil, err } - params := make(map[string]string) + params := url.Values{} if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { return nil, err } - params["filters"] = stringFilter + params.Set("filters", stringFilter) } response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params) if err != nil { @@ -130,9 +131,9 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if force != nil { - params["force"] = strconv.FormatBool(*force) + params.Set("force", strconv.FormatBool(*force)) } response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID) if err != nil { @@ -166,9 +167,9 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if timeout != nil { - params["t"] = strconv.Itoa(*timeout) + params.Set("t", strconv.Itoa(*timeout)) } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID) if err != nil { diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 98d64bbaa..38f5014ca 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -20,9 +20,18 @@ type testImage struct { } const ( + devPodmanBinaryLocation string = "../../../bin/podman" defaultPodmanBinaryLocation string = "/usr/bin/podman" ) +func getPodmanBinary() string { + _, err := os.Stat(devPodmanBinaryLocation) + if os.IsNotExist(err) { + return defaultPodmanBinaryLocation + } + return devPodmanBinaryLocation +} + var ( ImageCacheDir = "/tmp/podman/imagecachedir" LockTmpDir string @@ -50,7 +59,7 @@ type bindingTest struct { func (b *bindingTest) runPodman(command []string) *gexec.Session { var cmd []string - podmanBinary := defaultPodmanBinaryLocation + podmanBinary := getPodmanBinary() val, ok := os.LookupEnv("PODMAN_BINARY") if ok { podmanBinary = val @@ -166,7 +175,7 @@ func (b *bindingTest) restoreImageFromCache(i testImage) { // and add or append the alpine image to it func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) { cmd := []string{"run", "-dt"} - if *insidePod && podName != nil { + if insidePod != nil && podName != nil { pName := *podName cmd = append(cmd, "--pod", pName) } else if containerName != nil { diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 0b51c8c9e..8eef28502 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -3,6 +3,8 @@ package test_bindings import ( "context" "net/http" + "os" + "path/filepath" "time" "github.com/containers/libpod/pkg/bindings" @@ -71,6 +73,14 @@ var _ = Describe("Podman images", func() { // Inspect by long name _, err = images.GetImage(connText, alpine.name, nil) Expect(err).To(BeNil()) + // TODO it looks like the images API alwaays returns size regardless + // of bool or not. What should we do ? + //Expect(data.Size).To(BeZero()) + + // Enabling the size parameter should result in size being populated + data, err = images.GetImage(connText, alpine.name, &trueFlag) + Expect(err).To(BeNil()) + Expect(data.Size).To(BeNumerically(">", 0)) }) // Test to validate the remove image api @@ -181,4 +191,159 @@ var _ = Describe("Podman images", func() { Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) }) + It("Image Exists", func() { + // exists on bogus image should be false, with no error + exists, err := images.Exists(connText, "foobar") + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + + // exists with shortname should be true + exists, err = images.Exists(connText, alpine.shortName) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + + // exists with fqname should be true + exists, err = images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + }) + + It("Load|Import Image", func() { + // load an image + _, err := images.Remove(connText, alpine.name, nil) + Expect(err).To(BeNil()) + exists, err := images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) + defer f.Close() + Expect(err).To(BeNil()) + names, err := images.Load(connText, f, nil) + Expect(err).To(BeNil()) + Expect(names).To(Equal(alpine.name)) + exists, err = images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + + // load with a repo name + f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) + Expect(err).To(BeNil()) + _, err = images.Remove(connText, alpine.name, nil) + Expect(err).To(BeNil()) + exists, err = images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + newName := "quay.io/newname:fizzle" + names, err = images.Load(connText, f, &newName) + Expect(err).To(BeNil()) + Expect(names).To(Equal(alpine.name)) + exists, err = images.Exists(connText, newName) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + + // 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(connText, alpine.name, nil) + Expect(err).To(BeNil()) + exists, err = images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + badName := "quay.io/newName:fizzle" + _, err = images.Load(connText, f, &badName) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("Export Image", func() { + // Export an image + exportPath := filepath.Join(bt.tempDirPath, alpine.tarballName) + w, err := os.Create(filepath.Join(bt.tempDirPath, alpine.tarballName)) + defer w.Close() + Expect(err).To(BeNil()) + err = images.Export(connText, alpine.name, w, nil, nil) + Expect(err).To(BeNil()) + _, err = os.Stat(exportPath) + Expect(err).To(BeNil()) + + // TODO how do we verify that a format change worked? + }) + + It("Import Image", func() { + // load an image + _, err = images.Remove(connText, alpine.name, nil) + Expect(err).To(BeNil()) + exists, err := images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeFalse()) + f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) + defer f.Close() + Expect(err).To(BeNil()) + changes := []string{"CMD /bin/foobar"} + testMessage := "test_import" + _, err = images.Import(connText, changes, &testMessage, &alpine.name, nil, f) + Expect(err).To(BeNil()) + exists, err = images.Exists(connText, alpine.name) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + data, err := images.GetImage(connText, alpine.name, nil) + Expect(err).To(BeNil()) + Expect(data.Comment).To(Equal(testMessage)) + + }) + It("History Image", func() { + // a bogus name should return a 404 + _, err := images.History(connText, "foobar") + Expect(err).To(Not(BeNil())) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + var foundID bool + data, err := images.GetImage(connText, alpine.name, nil) + Expect(err).To(BeNil()) + history, err := images.History(connText, alpine.name) + Expect(err).To(BeNil()) + for _, i := range history { + if i.ID == data.ID { + foundID = true + break + } + } + Expect(foundID).To(BeTrue()) + }) + + It("Search for an image", func() { + imgs, err := images.Search(connText, "alpine", nil, nil) + Expect(err).To(BeNil()) + Expect(len(imgs)).To(BeNumerically(">", 1)) + var foundAlpine bool + for _, i := range imgs { + if i.Name == "docker.io/library/alpine" { + foundAlpine = true + break + } + } + Expect(foundAlpine).To(BeTrue()) + + // Search for alpine with a limit of 10 + ten := 10 + imgs, err = images.Search(connText, "docker.io/alpine", &ten, nil) + Expect(err).To(BeNil()) + Expect(len(imgs)).To(BeNumerically("<=", 10)) + + // Search for alpine with stars greater than 100 + filters := make(map[string][]string) + filters["stars"] = []string{"100"} + imgs, err = images.Search(connText, "docker.io/alpine", nil, filters) + Expect(err).To(BeNil()) + for _, i := range imgs { + Expect(i.Stars).To(BeNumerically(">=", 100)) + } + + // Search with a fqdn + imgs, err = images.Search(connText, "quay.io/libpod/alpine_nginx", nil, nil) + Expect(len(imgs)).To(BeNumerically(">=", 1)) + }) + }) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 8313a7460..7f6a9cc9b 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -3,6 +3,7 @@ package volumes import ( "context" "net/http" + "net/url" "strconv" "github.com/containers/libpod/libpod" @@ -73,9 +74,9 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { if err != nil { return err } - params := make(map[string]string) + params := url.Values{} if force != nil { - params["force"] = strconv.FormatBool(*force) + params.Set("force", strconv.FormatBool(*force)) } response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/prune", params, nameOrID) if err != nil { diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index 7633261e3..19a8658ac 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -41,6 +41,26 @@ var _ = Describe("Podman healthcheck run", func() { Expect(session).To(ExitWithError()) }) + It("podman disable healthcheck with --no-healthcheck on valid container", func() { + SkipIfRemote() + session := podmanTest.Podman([]string{"run", "-dt", "--no-healthcheck", "--name", "hc", healthcheck}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(125)) + }) + + It("podman disable healthcheck with --health-cmd=none on valid container", func() { + SkipIfRemote() + session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "none", "--name", "hc", healthcheck}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(125)) + }) + It("podman healthcheck on valid container", func() { Skip("Extremely consistent flake - re-enable on debugging") session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck}) diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats new file mode 100644 index 000000000..cd836610b --- /dev/null +++ b/test/system/500-networking.bats @@ -0,0 +1,63 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Test podman local networking +# + +load helpers + +# Copied from tsweeney's https://github.com/containers/libpod/issues/4827 +@test "podman networking: port on localhost" { + skip_if_remote + random_1=$(random_string 30) + random_2=$(random_string 30) + + HOST_PORT=8080 + SERVER=http://localhost:$HOST_PORT + + # Create a test file with random content + INDEX1=$PODMAN_TMPDIR/hello.txt + echo $random_1 > $INDEX1 + + # Bind-mount this file with a different name to a container running httpd + run_podman run -d --name myweb -p "$HOST_PORT:80" \ + -v $INDEX1:/var/www/index.txt \ + -w /var/www \ + busybox httpd -f -p 80 + cid=$output + + # In that container, create a second file, using exec and redirection + run_podman exec -i myweb sh -c "cat > index2.txt" <<<"$random_2" + # ...verify its contents as seen from container. + run_podman exec -i myweb cat /var/www/index2.txt + is "$output" "$random_2" "exec cat index2.txt" + + # Verify http contents: curl from localhost + run curl -s $SERVER/index.txt + is "$output" "$random_1" "curl localhost:/index.txt" + run curl -s $SERVER/index2.txt + is "$output" "$random_2" "curl localhost:/index2.txt" + + # Verify http contents: wget from a second container + run_podman run --rm --net=host busybox wget -qO - $SERVER/index.txt + is "$output" "$random_1" "podman wget /index.txt" + run_podman run --rm --net=host busybox wget -qO - $SERVER/index2.txt + is "$output" "$random_2" "podman wget /index2.txt" + + # Tests #4889 - two-argument form of "podman ports" was broken + run_podman port myweb + is "$output" "80/tcp -> 0.0.0.0:$HOST_PORT" "port <cid>" + run_podman port myweb 80 + is "$output" "0.0.0.0:$HOST_PORT" "port <cid> 80" + run_podman port myweb 80/tcp + is "$output" "0.0.0.0:$HOST_PORT" "port <cid> 80/tcp" + + run_podman 125 port myweb 99/tcp + is "$output" 'Error: failed to find published port "99/tcp"' + + # Clean up + run_podman stop -t 1 myweb + run_podman rm myweb + run_podman rmi busybox +} + +# vim: filetype=sh diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 9fcfd0867..0e97a0778 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -11,6 +11,7 @@ import ( "io" "io/ioutil" "os" + "path" "path/filepath" "regexp" "strconv" @@ -37,15 +38,14 @@ const ( selinuxTag = "SELINUX" xattrNameSelinux = "security.selinux" stRdOnly = 0x01 - selinuxfsMagic = 0xf97cff8c ) type selinuxState struct { - enabledSet bool - enabled bool - selinuxfsSet bool - selinuxfs string - mcsList map[string]bool + enabledSet bool + enabled bool + selinuxfsOnce sync.Once + selinuxfs string + mcsList map[string]bool sync.Mutex } @@ -62,6 +62,10 @@ var ( state = selinuxState{ mcsList: make(map[string]bool), } + + // for attrPath() + attrPathOnce sync.Once + haveThreadSelf bool ) // Context is a representation of the SELinux label broken into 4 parts @@ -98,14 +102,6 @@ func SetDisabled() { state.setEnable(false) } -func (s *selinuxState) setSELinuxfs(selinuxfs string) string { - s.Lock() - defer s.Unlock() - s.selinuxfsSet = true - s.selinuxfs = selinuxfs - return s.selinuxfs -} - func verifySELinuxfsMount(mnt string) bool { var buf syscall.Statfs_t for { @@ -118,7 +114,8 @@ func verifySELinuxfsMount(mnt string) bool { } return false } - if uint32(buf.Type) != uint32(selinuxfsMagic) { + + if buf.Type != unix.SELINUX_MAGIC { return false } if (buf.Flags & stRdOnly) != 0 { @@ -166,33 +163,29 @@ func findSELinuxfs() string { // if there is one, or an empty string in case of EOF or error. func findSELinuxfsMount(s *bufio.Scanner) string { for s.Scan() { - txt := s.Text() + txt := s.Bytes() // The first field after - is fs type. // Safe as spaces in mountpoints are encoded as \040 - if !strings.Contains(txt, " - selinuxfs ") { + if !bytes.Contains(txt, []byte(" - selinuxfs ")) { continue } const mPos = 5 // mount point is 5th field - fields := strings.SplitN(txt, " ", mPos+1) + fields := bytes.SplitN(txt, []byte(" "), mPos+1) if len(fields) < mPos+1 { continue } - return fields[mPos-1] + return string(fields[mPos-1]) } return "" } func (s *selinuxState) getSELinuxfs() string { - s.Lock() - selinuxfs := s.selinuxfs - selinuxfsSet := s.selinuxfsSet - s.Unlock() - if selinuxfsSet { - return selinuxfs - } + s.selinuxfsOnce.Do(func() { + s.selinuxfs = findSELinuxfs() + }) - return s.setSELinuxfs(findSELinuxfs()) + return s.selinuxfs } // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs @@ -254,10 +247,17 @@ func getSELinuxPolicyRoot() string { return filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) } -func isProcHandle(fh *os.File) (bool, error) { +func isProcHandle(fh *os.File) error { var buf unix.Statfs_t err := unix.Fstatfs(int(fh.Fd()), &buf) - return buf.Type == unix.PROC_SUPER_MAGIC, err + if err != nil { + return fmt.Errorf("statfs(%q) failed: %v", fh.Name(), err) + } + if buf.Type != unix.PROC_SUPER_MAGIC { + return fmt.Errorf("file %q is not on procfs", fh.Name()) + } + + return nil } func readCon(fpath string) (string, error) { @@ -271,10 +271,8 @@ func readCon(fpath string) (string, error) { } defer in.Close() - if ok, err := isProcHandle(in); err != nil { + if err := isProcHandle(in); err != nil { return "", err - } else if !ok { - return "", fmt.Errorf("%s not on procfs", fpath) } var retval string @@ -317,7 +315,7 @@ SetFSCreateLabel tells kernel the label to create all file system objects created by this task. Setting label="" to return to default. */ func SetFSCreateLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), label) + return writeAttr("fscreate", label) } /* @@ -325,12 +323,12 @@ FSCreateLabel returns the default label the kernel which the kernel is using for file system objects created by this task. "" indicates default. */ func FSCreateLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid())) + return readAttr("fscreate") } // CurrentLabel returns the SELinux label of the current process thread, or an error. func CurrentLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid())) + return readAttr("current") } // PidLabel returns the SELinux label of the given pid, or an error. @@ -343,10 +341,10 @@ ExecLabel returns the SELinux label that the kernel will use for any programs that are executed by the current process thread, or an error. */ func ExecLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid())) + return readAttr("exec") } -func writeCon(fpath string, val string) error { +func writeCon(fpath, val string) error { if fpath == "" { return ErrEmptyPath } @@ -362,10 +360,8 @@ func writeCon(fpath string, val string) error { } defer out.Close() - if ok, err := isProcHandle(out); err != nil { + if err := isProcHandle(out); err != nil { return err - } else if !ok { - return fmt.Errorf("%s not on procfs", fpath) } if val != "" { @@ -379,6 +375,32 @@ func writeCon(fpath string, val string) error { return nil } +func attrPath(attr string) string { + // Linux >= 3.17 provides this + const threadSelfPrefix = "/proc/thread-self/attr" + + attrPathOnce.Do(func() { + st, err := os.Stat(threadSelfPrefix) + if err == nil && st.Mode().IsDir() { + haveThreadSelf = true + } + }) + + if haveThreadSelf { + return path.Join(threadSelfPrefix, attr) + } + + return path.Join("/proc/self/task/", strconv.Itoa(syscall.Gettid()), "/attr/", attr) +} + +func readAttr(attr string) (string, error) { + return readCon(attrPath(attr)) +} + +func writeAttr(attr, val string) error { + return writeCon(attrPath(attr), val) +} + /* CanonicalizeContext takes a context string and writes it to the kernel the function then returns the context that the kernel will use. This function @@ -415,7 +437,7 @@ SetExecLabel sets the SELinux label that the kernel will use for any programs that are executed by the current process thread, or an error. */ func SetExecLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), label) + return writeAttr("exec", label) } /* @@ -423,18 +445,18 @@ SetTaskLabel sets the SELinux label for the current thread, or an error. This requires the dyntransition permission. */ func SetTaskLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()), label) + return writeAttr("current", label) } // SetSocketLabel takes a process label and tells the kernel to assign the // label to the next socket that gets created func SetSocketLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/sockcreate", syscall.Gettid()), label) + return writeAttr("sockcreate", label) } // SocketLabel retrieves the current socket label setting func SocketLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/sockcreate", syscall.Gettid())) + return readAttr("sockcreate") } // PeerLabel retrieves the label of the client on the other side of a socket @@ -449,7 +471,7 @@ func SetKeyLabel(label string) error { if os.IsNotExist(err) { return nil } - if label == "" && os.IsPermission(err) && !GetEnabled() { + if label == "" && os.IsPermission(err) { return nil } return err @@ -505,19 +527,18 @@ func ReserveLabel(label string) { } func selinuxEnforcePath() string { - return fmt.Sprintf("%s/enforce", getSelinuxMountPoint()) + return path.Join(getSelinuxMountPoint(), "enforce") } // EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled func EnforceMode() int { var enforce int - enforceS, err := readCon(selinuxEnforcePath()) + enforceB, err := ioutil.ReadFile(selinuxEnforcePath()) if err != nil { return -1 } - - enforce, err = strconv.Atoi(string(enforceS)) + enforce, err = strconv.Atoi(string(enforceB)) if err != nil { return -1 } @@ -529,7 +550,7 @@ SetEnforceMode sets the current SELinux mode Enforcing, Permissive. Disabled is not valid, since this needs to be set at boot time. */ func SetEnforceMode(mode int) error { - return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode)) + return ioutil.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0644) } /* @@ -711,7 +732,7 @@ exit: // SecurityCheckContext validates that the SELinux label is understood by the kernel func SecurityCheckContext(val string) error { - return writeCon(fmt.Sprintf("%s/context", getSelinuxMountPoint()), val) + return ioutil.WriteFile(path.Join(getSelinuxMountPoint(), "context"), []byte(val), 0644) } /* diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go index 67a9d8ee8..4e711a9f8 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go @@ -3,76 +3,32 @@ package selinux import ( - "syscall" - "unsafe" + "golang.org/x/sys/unix" ) -var _zero uintptr - // Returns a []byte slice if the xattr is set and nil otherwise // Requires path and its attribute as arguments func lgetxattr(path string, attr string) ([]byte, error) { - var sz int - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return nil, err - } - attrBytes, err := syscall.BytePtrFromString(attr) - if err != nil { - return nil, err - } - // Start with a 128 length byte array - sz = 128 - dest := make([]byte, sz) - destBytes := unsafe.Pointer(&dest[0]) - _sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) - - switch { - case errno == syscall.ENODATA: - return nil, errno - case errno == syscall.ENOTSUP: - return nil, errno - case errno == syscall.ERANGE: - // 128 byte array might just not be good enough, - // A dummy buffer is used ``uintptr(0)`` to get real size - // of the xattrs on disk - _sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0) - sz = int(_sz) - if sz < 0 { + dest := make([]byte, 128) + sz, errno := unix.Lgetxattr(path, attr, dest) + if errno == unix.ERANGE { + // Buffer too small, get the real size first + sz, errno = unix.Lgetxattr(path, attr, []byte{}) + if errno != nil { return nil, errno } + dest = make([]byte, sz) - destBytes := unsafe.Pointer(&dest[0]) - _sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) - if errno != 0 { - return nil, errno - } - case errno != 0: + sz, errno = unix.Lgetxattr(path, attr, dest) + } + if errno != nil { return nil, errno } - sz = int(_sz) + return dest[:sz], nil } func lsetxattr(path string, attr string, data []byte, flags int) error { - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - attrBytes, err := syscall.BytePtrFromString(attr) - if err != nil { - return err - } - var dataBytes unsafe.Pointer - if len(data) > 0 { - dataBytes = unsafe.Pointer(&data[0]) - } else { - dataBytes = unsafe.Pointer(&_zero) - } - _, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0) - if errno != 0 { - return errno - } - return nil + return unix.Lsetxattr(path, attr, data, flags) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 2d5e80c2e..e9ef9873f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -405,7 +405,7 @@ github.com/opencontainers/runtime-tools/generate github.com/opencontainers/runtime-tools/generate/seccomp github.com/opencontainers/runtime-tools/specerror github.com/opencontainers/runtime-tools/validate -# github.com/opencontainers/selinux v1.3.1 +# github.com/opencontainers/selinux v1.3.2 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label # github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316 |