diff options
186 files changed, 4189 insertions, 897 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 3d1784303..392d7b72d 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -28,15 +28,15 @@ env: #### Cache-image names to test with ### ACTIVE_CACHE_IMAGE_NAMES: >- - fedora-28-libpod-6318419153518592 - fedora-29-libpod-6318419153518592 - ubuntu-18-libpod-6318419153518592 - rhel-7-libpod-6318419153518592 + fedora-29-libpod-548c1c05 + fedora-28-libpod-548c1c05 + ubuntu-18-libpod-548c1c05 + rhel-7-libpod-548c1c05 image-builder-image-1541772081 - FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-6318419153518592" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-6318419153518592" - UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-6318419153518592" - PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-6318419153518592" + FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-548c1c05" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-548c1c05" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-548c1c05" + PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-548c1c05" # RHEL_CACHE_IMAGE_NAME: "rhel-8-notready" # CENTOS_CACHE_IMAGE_NAME: "centos-7-notready" @@ -49,7 +49,7 @@ env: CNI_COMMIT: "7480240de9749f9a0a5c8614b17f1f03e0c06ab9" CRIO_COMMIT: "7a283c391abb7bd25086a8ff91dbb36ebdd24466" CRIU_COMMIT: "c74b83cd49c00589c0c0468ba5fe685b67fdbd0a" - RUNC_COMMIT: "25f3f893c86d07426df93b7aa172f33fdf093fbd" + RUNC_COMMIT: "029124da7af7360afa781a0234d1b083550f797c" # CSV of cache-image names to build (see $PACKER_BASE/libpod_images.json) PACKER_BUILDS: "ubuntu-18,fedora-29,fedora-28,rhel-7" # TODO: rhel-8,centos-7 # Version of packer to use diff --git a/Dockerfile b/Dockerfile index 83cd3fccd..767e64570 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ RUN apt-get update && apt-get install -y \ && apt-get clean # Install runc -ENV RUNC_COMMIT 96ec2177ae841256168fcf76954f7177af9446eb +ENV RUNC_COMMIT 029124da7af7360afa781a0234d1b083550f797c RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \ @@ -152,6 +152,12 @@ libpodimage: ## Build the libpod image dbuild: libpodimage ${CONTAINER_RUNTIME} run --name=${LIBPOD_INSTANCE} --privileged -v ${PWD}:/go/src/${PROJECT} --rm ${LIBPOD_IMAGE} make all +dbuild-podman-remote: libpodimage + ${CONTAINER_RUNTIME} run --name=${LIBPOD_INSTANCE} --privileged -v ${PWD}:/go/src/${PROJECT} --rm ${LIBPOD_IMAGE} go build -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS) remoteclient" -o bin/podman-remote $(PROJECT)/cmd/podman + +dbuild-podman-remote-darwin: libpodimage + ${CONTAINER_RUNTIME} run --name=${LIBPOD_INSTANCE} --privileged -v ${PWD}:/go/src/${PROJECT} --rm ${LIBPOD_IMAGE} env GOOS=darwin go build -ldflags '$(LDFLAGS_PODMAN)' -tags "remoteclient containers_image_openpgp exclude_graphdriver_devicemapper" -o bin/podman-remote-darwin $(PROJECT)/cmd/podman + test: libpodimage ## Run tests on built image ${CONTAINER_RUNTIME} run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e OCI_RUNTIME -e CGROUP_MANAGER=cgroupfs -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make clean all localunit install.catatonit localintegration @@ -45,7 +45,11 @@ This project tests all builds against each supported version of Fedora, the late Podman can also generate Kubernetes YAML based on a container or Pod (see [podman-generate-kube](https://github.com/containers/libpod/blob/master/docs/podman-generate-kube.1.md)), which allows for an easy transition from a local development environment - to a production Kubernetes cluster. + to a production Kubernetes cluster. If Kubernetes does not fit your requirements, + there are other third-party tools that support the docker-compose format such as + [kompose](https://github.com/kubernetes/kompose/) and + [podman-compose](https://github.com/muayyad-alsadi/podman-compose) + that might be appropriate for your environment. ## OCI Projects Plans diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index c07c0f1cf..37f8afbad 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -47,7 +47,7 @@ func attachCmd(c *cliconfig.AttachValues) error { if remoteclient && len(c.InputArgs) != 1 { return errors.Errorf("attach requires the name or id of one running container") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating runtime") } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index b69ac6e84..647ff1e86 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -206,7 +206,7 @@ func buildCmd(c *cliconfig.BuildValues) error { dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index f2f5d37da..234d683bb 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -54,7 +54,7 @@ func checkpointCmd(c *cliconfig.CheckpointValues) error { return errors.New("checkpointing a container requires root") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index f5b3cf55b..4ff744ae5 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -1,11 +1,8 @@ package main import ( - "fmt" - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -49,38 +46,16 @@ func init() { } func cleanupCmd(c *cliconfig.CleanupValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - cleanupContainers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") - - ctx := getContext() - - for _, ctr := range cleanupContainers { - hadError := false - if c.Remove { - if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) - hadError = true - } - } else { - if err := ctr.Cleanup(ctx); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) - hadError = true - } - } - if !hadError { - fmt.Println(ctr.ID()) - } + ok, failures, err := runtime.CleanupContainers(getContext(), c) + if err != nil { + return err } - return lastError + + return printCmdResults(ok, failures) } diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 640a4bff4..77156f47a 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -581,6 +581,10 @@ type SystemRenumberValues struct { PodmanCommand } +type SystemMigrateValues struct { + PodmanCommand +} + type SystemDfValues struct { PodmanCommand Verbose bool diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index c36452cfe..4b0641d82 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -19,10 +19,8 @@ func getMainCommands() []*cobra.Command { _mountCommand, _portCommand, _refreshCommand, - _restartCommand, _searchCommand, _statsCommand, - _topCommand, } if len(_varlinkCommand.Use) > 0 { @@ -48,14 +46,10 @@ func getContainerSubCommands() []*cobra.Command { _execCommand, _mountCommand, _portCommand, - _pruneContainersCommand, _refreshCommand, - _restartCommand, _restoreCommand, _runlabelCommand, _statsCommand, - _stopCommand, - _topCommand, _umountCommand, } } @@ -78,9 +72,9 @@ func getTrustSubCommands() []*cobra.Command { // Commands that the local client implements func getSystemSubCommands() []*cobra.Command { return []*cobra.Command{ - _pruneSystemCommand, _renumberCommand, _dfSystemCommand, + _migrateCommand, } } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 8d79c1e28..2b38bab35 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -52,7 +52,7 @@ func init() { } func commitCmd(c *cliconfig.CommitValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/common.go b/cmd/podman/common.go index ba4a3f519..eac96d3ba 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -434,6 +434,10 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "read-only", false, "Make containers root filesystem read-only", ) + createFlags.Bool( + "read-only-tmpfs", true, + "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", + ) createFlags.String( "restart", "", "Restart is not supported. Please use a systemd unit file for restart", diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 7733c8eef..b3058bf12 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -60,9 +60,13 @@ var ( _listSubCommand, _logsCommand, _pauseCommand, + _restartCommand, + _pruneContainersCommand, _runCommand, _rmCommand, _startCommand, + _stopCommand, + _topCommand, _unpauseCommand, _waitCommand, } diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index abc56cee1..b052bda36 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -1,14 +1,11 @@ package main import ( - "context" - "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -41,51 +38,30 @@ func init() { flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") } -func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force, volumes bool) error { - var deleteFuncs []shared.ParallelWorkerInput - - filter := func(c *libpod.Container) bool { - state, err := c.State() - if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") { - return true - } - return false - } - delContainers, err := runtime.GetContainers(filter) - if err != nil { - return err - } - if len(delContainers) < 1 { - return nil - } - for _, container := range delContainers { - con := container - f := func() error { - return runtime.RemoveContainer(ctx, con, force, volumes) - } - - deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) - } - // Run the parallel funcs - deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - return printParallelOutput(deleteErrors, errCount) -} - func pruneContainersCmd(c *cliconfig.PruneContainersValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - maxWorkers := shared.Parallelize("rm") + maxWorkers := shared.DefaultPoolSize("prune") if c.GlobalIsSet("max-workers") { maxWorkers = c.GlobalFlags.MaxWorks } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"), c.Bool("volumes")) + ok, failures, err := runtime.Prune(getContext(), maxWorkers, c.Force) + if err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + if len(c.InputArgs) > 1 { + exitCode = 125 + } else { + exitCode = 1 + } + } + return err + } + if len(failures) > 0 { + exitCode = 125 + } + return printCmdResults(ok, failures) } diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 6e48b9f3b..82f2d3f20 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -58,7 +58,7 @@ func cpCmd(c *cliconfig.CpValues) error { return errors.Errorf("you must provide a source path and a destination path") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/create.go b/cmd/podman/create.go index cfc0fa0c3..cb3ba14c5 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -52,7 +52,7 @@ func createCmd(c *cliconfig.CreateValues) error { return err } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 1138c48a3..9543113d8 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -87,7 +87,7 @@ func diffCmd(c *cliconfig.DiffValues) error { return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/events.go b/cmd/podman/events.go index 4c11fe1f3..15f5e9571 100644 --- a/cmd/podman/events.go +++ b/cmd/podman/events.go @@ -40,7 +40,7 @@ func init() { } func eventsCmd(c *cliconfig.EventValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index d0d88ee8b..deff44a92 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -68,7 +68,7 @@ func execCmd(c *cliconfig.ExecValues) error { argStart = 0 } cmd := args[argStart:] - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index dae48f14b..6619522b6 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -86,7 +86,7 @@ func imageExistsCmd(c *cliconfig.ImageExistsValues) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one image at a time") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -107,7 +107,7 @@ func containerExistsCmd(c *cliconfig.ContainerExistsValues) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one container at a time") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -126,7 +126,7 @@ func podExistsCmd(c *cliconfig.PodExistsValues) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one pod at a time") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/export.go b/cmd/podman/export.go index 004c3ccde..82a4c13e7 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -41,7 +41,7 @@ func init() { // exportCmd saves a container to a tarball on disk func exportCmd(c *cliconfig.ExportValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 7963cde6e..318dd0771 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -54,7 +54,7 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { return errors.Errorf("you must provide exactly one container|pod ID or name") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/healthcheck_run.go b/cmd/podman/healthcheck_run.go index 832451e0c..111318d9c 100644 --- a/cmd/podman/healthcheck_run.go +++ b/cmd/podman/healthcheck_run.go @@ -38,7 +38,7 @@ func init() { } func healthCheckCmd(c *cliconfig.HealthCheckValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrap(err, "could not get runtime") } diff --git a/cmd/podman/history.go b/cmd/podman/history.go index f96d7934c..cebf99a9f 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -67,7 +67,7 @@ func init() { } func historyCmd(c *cliconfig.HistoryValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/images.go b/cmd/podman/images.go index f584c1131..1c46571c3 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -134,7 +134,7 @@ func imagesCmd(c *cliconfig.ImagesValues) error { image string ) - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "Could not get runtime") } @@ -243,7 +243,7 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma // If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent // to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag // is not set. - isParent, err := img.IsParent() + isParent, err := img.IsParent(ctx) if err != nil { logrus.Errorf("error checking if image is a parent %q: %v", img.ID(), err) } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 84181d0a2..1ac5bc65d 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -37,7 +37,7 @@ func init() { } func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -45,7 +45,7 @@ func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.All) + pruneCids, err := runtime.PruneImages(getContext(), c.All) if len(pruneCids) > 0 { for _, cid := range pruneCids { fmt.Println(cid) diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 2bba6cb0c..167d9f2c9 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -45,7 +45,7 @@ func init() { } func importCmd(c *cliconfig.ImportValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 2b6ae1882..a6fce7fcb 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -50,7 +50,7 @@ func infoCmd(c *cliconfig.InfoValues) error { info := map[string]interface{}{} remoteClientInfo := map[string]interface{}{} - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 9491bc7c7..4303c149c 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -84,7 +84,7 @@ func inspectCmd(c *cliconfig.InspectValues) error { return errors.Errorf("you cannot provide additional arguments with --latest") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 0513a154f..edf69ff2e 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -59,7 +59,7 @@ func killCmd(c *cliconfig.KillValues) error { return err } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 78adf1252..b03846bbc 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -1,6 +1,8 @@ package libpodruntime import ( + "context" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" @@ -9,17 +11,22 @@ import ( "github.com/pkg/errors" ) +// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers +func GetRuntimeMigrate(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { + return getRuntime(ctx, c, false, true) +} + // GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber -func GetRuntimeRenumber(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(c, true) +func GetRuntimeRenumber(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { + return getRuntime(ctx, c, true, false) } // GetRuntime generates a new libpod runtime configured by command line options -func GetRuntime(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { - return getRuntime(c, false) +func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { + return getRuntime(ctx, c, false, false) } -func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, error) { +func getRuntime(ctx context.Context, c *cliconfig.PodmanCommand, renumber bool, migrate bool) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} storageOpts := storage.StoreOptions{} storageSet := false @@ -63,11 +70,16 @@ func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, err storageSet = true storageOpts.GraphDriverOptions = c.GlobalFlags.StorageOpts } + if migrate { + options = append(options, libpod.WithMigrate()) + } if renumber { options = append(options, libpod.WithRenumber()) } + options = append(options, libpod.WithContext(ctx)) + // Only set this if the user changes storage config on the command line if storageSet { options = append(options, libpod.WithStorageConfig(storageOpts)) diff --git a/cmd/podman/load.go b/cmd/podman/load.go index 3cc5e67c7..f3bbed48f 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -58,7 +58,7 @@ func loadCmd(c *cliconfig.LoadValues) error { return errors.New("too many arguments. Requires exactly 1") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 589255683..6bf148cca 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -11,6 +11,7 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/image" + "github.com/docker/docker-credential-helpers/credentials" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" @@ -90,7 +91,8 @@ func loginCmd(c *cliconfig.LoginValues) error { // username of user logged in to server (if one exists) userFromAuthFile, passFromAuthFile, err := config.GetAuthentication(sc, server) - if err != nil { + // Do not return error if no credentials found in credHelpers, new credentials will be stored by config.SetAuthentication + if err != nil && err != credentials.NewErrCredentialsNotFound() { return errors.Wrapf(err, "error reading auth file") } diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 6f24dc8fb..a1ec9f4ee 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -64,7 +64,7 @@ func init() { func logsCmd(c *cliconfig.LogsValues) error { var err error - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 15f4a5d71..a0f1cf401 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -50,12 +50,14 @@ var mainCommands = []*cobra.Command{ &_psCommand, _pullCommand, _pushCommand, + _restartCommand, _rmCommand, &_rmiCommand, _runCommand, _saveCommand, _stopCommand, _tagCommand, + _topCommand, _umountCommand, _unpauseCommand, _versionCommand, diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go index 91ad42630..5afd51e28 100644 --- a/cmd/podman/main_local.go +++ b/cmd/podman/main_local.go @@ -103,7 +103,7 @@ func profileOff(cmd *cobra.Command) error { } func setupRootless(cmd *cobra.Command, args []string) error { - if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || strings.HasPrefix(cmd.Use, "help") { + if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || cmd == _migrateCommand || strings.HasPrefix(cmd.Use, "help") { return nil } podmanCmd := cliconfig.PodmanCommand{ @@ -112,7 +112,7 @@ func setupRootless(cmd *cobra.Command, args []string) error { MainGlobalOpts, remoteclient, } - runtime, err := libpodruntime.GetRuntime(&podmanCmd) + runtime, err := libpodruntime.GetRuntime(getContext(), &podmanCmd) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index 2ade8949a..7c9150d1b 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -61,7 +61,7 @@ type jsonMountPoint struct { } func mountCmd(c *cliconfig.MountValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index ca137150a..cd8370082 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -43,7 +43,7 @@ func pauseCmd(c *cliconfig.PauseValues) error { return errors.New("pause is not supported for rootless containers") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index d1008e615..967798399 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -75,7 +75,7 @@ func playKubeCmd(c *cliconfig.KubePlayValues) error { } ctx := getContext() - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(ctx, &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 551010dce..c891f2c7b 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -62,7 +62,7 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error { podIdFile *os.File ) - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go index eb2366031..a22624078 100644 --- a/cmd/podman/pod_inspect.go +++ b/cmd/podman/pod_inspect.go @@ -49,7 +49,7 @@ func podInspectCmd(c *cliconfig.PodInspectValues) error { return errors.Errorf("you must provide the name or id of a pod") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index 145d0492f..c1ea66126 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -49,7 +49,7 @@ func init() { // podKillCmd kills one or more pods with a signal func podKillCmd(c *cliconfig.PodKillValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go index 1c6611ebc..e8574bfdc 100644 --- a/cmd/podman/pod_pause.go +++ b/cmd/podman/pod_pause.go @@ -45,7 +45,7 @@ func init() { func podPauseCmd(c *cliconfig.PodPauseValues) error { var lastError error - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index f4b7437eb..b9dcbc05d 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -157,7 +157,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { return errors.Wrapf(err, "error with flags passed") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index 519568974..a1f4c8359 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -47,7 +47,7 @@ func init() { func podRestartCmd(c *cliconfig.PodRestartValues) error { var lastError error - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index dd67bb0e0..218ed8154 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -47,7 +47,7 @@ func init() { // podRmCmd deletes pods func podRmCmd(c *cliconfig.PodRmValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_start.go b/cmd/podman/pod_start.go index 104f9ad73..5c9225428 100644 --- a/cmd/podman/pod_start.go +++ b/cmd/podman/pod_start.go @@ -45,7 +45,7 @@ func init() { } func podStartCmd(c *cliconfig.PodStartValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index ed59d9a61..e0e5ca24e 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -78,7 +78,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { all = true } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 9cd425c29..b4b1718d9 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -47,7 +47,7 @@ func init() { } func podStopCmd(c *cliconfig.PodStopValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index e997d1456..64e32318e 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -67,7 +67,7 @@ func podTopCmd(c *cliconfig.PodTopValues) error { return errors.Errorf("you must provide the name or id of a running pod") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go index 15375bee9..c5b7e6a18 100644 --- a/cmd/podman/pod_unpause.go +++ b/cmd/podman/pod_unpause.go @@ -46,7 +46,7 @@ func init() { func podUnpauseCmd(c *cliconfig.PodUnpauseValues) error { var lastError error - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pods_prune.go b/cmd/podman/pods_prune.go index e6946f068..bdd75f9de 100644 --- a/cmd/podman/pods_prune.go +++ b/cmd/podman/pods_prune.go @@ -36,7 +36,7 @@ func init() { } func podPruneCmd(c *cliconfig.PodPruneValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/port.go b/cmd/podman/port.go index d63ae4aa9..7a9f01fe6 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -98,7 +98,7 @@ func portCmd(c *cliconfig.PortValues) error { } } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index df1ea2765..623f17050 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -211,7 +211,7 @@ func psCmd(c *cliconfig.PsValues) error { return errors.Wrapf(err, "error with flags passed") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 521419e7a..f6a5beb17 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -73,7 +73,7 @@ func pullCmd(c *cliconfig.PullValues) (retError error) { defer span.Finish() } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/push.go b/cmd/podman/push.go index e6beaaeb4..ee14b15e2 100644 --- a/cmd/podman/push.go +++ b/cmd/podman/push.go @@ -100,7 +100,7 @@ func pushCmd(c *cliconfig.PushValues) error { registryCreds = creds } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/refresh.go b/cmd/podman/refresh.go index 6640d9954..9f9cbf908 100644 --- a/cmd/podman/refresh.go +++ b/cmd/podman/refresh.go @@ -38,7 +38,7 @@ func init() { } func refreshCmd(c *cliconfig.RefreshValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 5a9f3043a..437676eef 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -2,11 +2,9 @@ package main import ( "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -22,7 +20,6 @@ var ( RunE: func(cmd *cobra.Command, args []string) error { restartCommand.InputArgs = args restartCommand.GlobalFlags = MainGlobalOpts - restartCommand.Remote = remoteclient return restartCmd(&restartCommand) }, Args: func(cmd *cobra.Command, args []string) error { @@ -49,83 +46,30 @@ func init() { } func restartCmd(c *cliconfig.RestartValues) error { - var ( - restartFuncs []shared.ParallelWorkerInput - containers []*libpod.Container - restartContainers []*libpod.Container - ) - - args := c.InputArgs - runOnly := c.Running all := c.All - if len(args) < 1 && !c.Latest && !all { + if len(c.InputArgs) < 1 && !c.Latest && !all { return errors.Wrapf(libpod.ErrInvalidArg, "you must provide at least one container name or ID") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - timeout := c.Timeout - useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed - - // Handle --latest - if c.Latest { - lastCtr, err := runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest container") - } - restartContainers = append(restartContainers, lastCtr) - } else if runOnly { - containers, err = getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") - if err != nil { - return err - } - restartContainers = append(restartContainers, containers...) - } else if all { - containers, err = runtime.GetAllContainers() - if err != nil { - return err - } - restartContainers = append(restartContainers, containers...) - } else { - for _, id := range args { - ctr, err := runtime.LookupContainer(id) - if err != nil { - return err + ok, failures, err := runtime.Restart(getContext(), c) + if err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + if len(c.InputArgs) > 1 { + exitCode = 125 + } else { + exitCode = 1 } - restartContainers = append(restartContainers, ctr) } + return err } - - maxWorkers := shared.Parallelize("restart") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks + if len(failures) > 0 { + exitCode = 125 } - - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - // We now have a slice of all the containers to be restarted. Iterate them to - // create restart Funcs with a timeout as needed - for _, ctr := range restartContainers { - con := ctr - ctrTimeout := ctr.StopTimeout() - if useTimeout { - ctrTimeout = timeout - } - - f := func() error { - return con.RestartWithTimeout(getContext(), ctrTimeout) - } - - restartFuncs = append(restartFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) - } - - restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) - return printParallelOutput(restartErrors, errCount) + return printCmdResults(ok, failures) } diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index d9e85c267..8cfd5ca0d 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -54,7 +54,7 @@ func restoreCmd(c *cliconfig.RestoreValues) error { return errors.New("restoring a container requires root") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index e3ee186ce..1bf56b782 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -48,7 +48,7 @@ func init() { // rmCmd removes one or more containers func rmCmd(c *cliconfig.RmValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 7ec875d5b..4c41a3ad5 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -51,7 +51,7 @@ func rmiCmd(c *cliconfig.RmiValues) error { ctx := getContext() removeAll := c.All - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -97,7 +97,7 @@ func rmiCmd(c *cliconfig.RmiValues) error { return errors.New("unable to delete all images; re-run the rmi command again.") } for _, i := range imagesToDelete { - isParent, err := i.IsParent() + isParent, err := i.IsParent(ctx) if err != nil { return err } diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 717a36e04..01b12d282 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -48,7 +48,7 @@ func runCmd(c *cliconfig.RunValues) error { return err } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index 8267e941f..f097cb693 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -85,7 +85,7 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error { } opts := make(map[string]string) - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index a45223b6a..4d204337e 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -70,7 +70,7 @@ func saveCmd(c *cliconfig.SaveValues) error { return errors.Errorf("need at least 1 argument") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index e14276bdf..9050fd2b9 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -658,7 +658,8 @@ func GetCtrInspectInfo(config *libpod.ContainerConfig, ctrInspectData *inspect.C OomKillDisable: memDisableOOMKiller, PidsLimit: pidsLimit, Privileged: config.Privileged, - ReadonlyRootfs: spec.Root.Readonly, + ReadOnlyRootfs: spec.Root.Readonly, + ReadOnlyTmpfs: createArtifact.ReadOnlyTmpfs, Runtime: config.OCIRuntime, NetworkMode: string(createArtifact.NetMode), IpcMode: string(createArtifact.IpcMode), diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 3f54e193f..c521f9cb6 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -650,6 +650,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. PortBindings: portBindings, Quiet: c.Bool("quiet"), ReadOnlyRootfs: c.Bool("read-only"), + ReadOnlyTmpfs: c.Bool("read-only-tmpfs"), Resources: cc.CreateResourceConfig{ BlkioWeight: blkioWeight, BlkioWeightDevice: c.StringSlice("blkio-weight-device"), diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go index 2e1827561..9c494dec5 100644 --- a/cmd/podman/shared/intermediate.go +++ b/cmd/podman/shared/intermediate.go @@ -434,6 +434,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes m["publish-all"] = newCRBool(c, "publish-all") m["quiet"] = newCRBool(c, "quiet") m["read-only"] = newCRBool(c, "read-only") + m["read-only-tmpfs"] = newCRBool(c, "read-only-tmpfs") m["restart"] = newCRString(c, "restart") m["rm"] = newCRBool(c, "rm") m["rootfs"] = newCRBool(c, "rootfs") diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go index d62a65955..5e21245e3 100644 --- a/cmd/podman/shared/intermediate_varlink.go +++ b/cmd/podman/shared/intermediate_varlink.go @@ -141,6 +141,7 @@ func (g GenericCLIResults) MakeVarlink() iopodman.Create { PublishAll: BoolToPtr(g.Find("publish-all")), Quiet: BoolToPtr(g.Find("quiet")), Readonly: BoolToPtr(g.Find("read-only")), + Readonlytmpfs: BoolToPtr(g.Find("read-only-tmpfs")), Restart: StringToPtr(g.Find("restart")), Rm: BoolToPtr(g.Find("rm")), Rootfs: BoolToPtr(g.Find("rootfs")), @@ -397,6 +398,7 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { m["publish-all"] = boolFromVarlink(opts.PublishAll, "publish-all", false) m["quiet"] = boolFromVarlink(opts.Quiet, "quiet", false) m["read-only"] = boolFromVarlink(opts.Readonly, "read-only", false) + m["read-only-tmpfs"] = boolFromVarlink(opts.Readonlytmpfs, "read-only-tmpfs", true) m["restart"] = stringFromVarlink(opts.Restart, "restart", nil) m["rm"] = boolFromVarlink(opts.Rm, "rm", false) m["rootfs"] = boolFromVarlink(opts.Rootfs, "rootfs", false) diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index b19b6a840..0c25eec62 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -56,7 +56,7 @@ func signCmd(c *cliconfig.SignValues) error { if len(args) < 1 { return errors.Errorf("at least one image name must be specified") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/start.go b/cmd/podman/start.go index 9f93061f9..bd34010f2 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -65,7 +65,7 @@ func startCmd(c *cliconfig.StartValues) error { return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index 6aa0cc10c..c2b2a688c 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -88,7 +88,7 @@ func statsCmd(c *cliconfig.StatsValues) error { return errors.Errorf("you must specify --all, --latest, or at least one container") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index f263bb166..d88c90deb 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -56,7 +56,7 @@ func stopCmd(c *cliconfig.StopValues) error { defer span.Finish() } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/system.go b/cmd/podman/system.go index 528a594de..80080bf44 100644 --- a/cmd/podman/system.go +++ b/cmd/podman/system.go @@ -20,6 +20,7 @@ var ( var systemCommands = []*cobra.Command{ _infoCommand, + _pruneSystemCommand, } func init() { diff --git a/cmd/podman/system_df.go b/cmd/podman/system_df.go index 16a8ad120..840916547 100644 --- a/cmd/podman/system_df.go +++ b/cmd/podman/system_df.go @@ -99,7 +99,7 @@ func init() { } func dfSystemCmd(c *cliconfig.SystemDfValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "Could not get runtime") } @@ -201,7 +201,7 @@ func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uin for _, img := range images { parentImg := img for { - next, err := parentImg.GetParent() + next, err := parentImg.GetParent(ctx) if err != nil { return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID()) } @@ -246,11 +246,11 @@ func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCi unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap) - isParent, err := img.IsParent() + isParent, err := img.IsParent(ctx) if err != nil { return imageDiskUsage, err } - parent, err := img.GetParent() + parent, err := img.GetParent(ctx) if err != nil { return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) } @@ -437,11 +437,11 @@ func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, images return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images") } for _, img := range images { - isParent, err := img.IsParent() + isParent, err := img.IsParent(ctx) if err != nil { return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID()) } - parent, err := img.GetParent() + parent, err := img.GetParent(ctx) if err != nil { return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) } diff --git a/cmd/podman/system_migrate.go b/cmd/podman/system_migrate.go new file mode 100644 index 000000000..4a0afcfad --- /dev/null +++ b/cmd/podman/system_migrate.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + migrateCommand cliconfig.SystemMigrateValues + migrateDescription = ` + podman system migrate + + Migrate existing containers to a new version of Podman. +` + + _migrateCommand = &cobra.Command{ + Use: "migrate", + Args: noSubArgs, + Short: "Migrate containers", + Long: migrateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + migrateCommand.InputArgs = args + migrateCommand.GlobalFlags = MainGlobalOpts + return migrateCmd(&migrateCommand) + }, + } +) + +func init() { + migrateCommand.Command = _migrateCommand + migrateCommand.SetHelpTemplate(HelpTemplate()) + migrateCommand.SetUsageTemplate(UsageTemplate()) +} + +func migrateCmd(c *cliconfig.SystemMigrateValues) error { + // We need to pass one extra option to NewRuntime. + // This will inform the OCI runtime to start a migrate. + // That's controlled by the last argument to GetRuntime. + r, err := libpodruntime.GetRuntimeMigrate(getContext(), &c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "error migrating containers") + } + if err := r.Shutdown(false); err != nil { + return err + } + + return nil +} diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index 8900e2644..d5b218cd8 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -72,7 +72,7 @@ Are you sure you want to continue? [y/N] `, volumeString) } } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -81,14 +81,15 @@ Are you sure you want to continue? [y/N] `, volumeString) rmWorkers := shared.Parallelize("rm") ctx := getContext() fmt.Println("Deleted Containers") - lasterr := pruneContainers(runtime, ctx, rmWorkers, false, false) + ok, failures, lasterr := runtime.Prune(ctx, rmWorkers, false) + printCmdResults(ok, failures) fmt.Println("Deleted Pods") pruneValues := cliconfig.PodPruneValues{ PodmanCommand: c.PodmanCommand, Force: c.Force, } - ok, failures, err := runtime.PrunePods(ctx, &pruneValues) + ok, failures, err = runtime.PrunePods(ctx, &pruneValues) if err != nil { if lasterr != nil { logrus.Errorf("%q", lasterr) @@ -110,7 +111,7 @@ Are you sure you want to continue? [y/N] `, volumeString) // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.All) + pruneCids, err := runtime.PruneImages(ctx, c.All) if len(pruneCids) > 0 { fmt.Println("Deleted Images") for _, cid := range pruneCids { diff --git a/cmd/podman/system_renumber.go b/cmd/podman/system_renumber.go index 70ba706bb..81752a177 100644 --- a/cmd/podman/system_renumber.go +++ b/cmd/podman/system_renumber.go @@ -40,7 +40,7 @@ func renumberCmd(c *cliconfig.SystemRenumberValues) error { // We need to pass one extra option to NewRuntime. // This will inform the OCI runtime to start a renumber. // That's controlled by the last argument to GetRuntime. - r, err := libpodruntime.GetRuntimeRenumber(&c.PodmanCommand) + r, err := libpodruntime.GetRuntimeRenumber(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error renumbering locks") } diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index deda4e985..58f221e26 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -38,7 +38,7 @@ func tagCmd(c *cliconfig.TagValues) error { if len(args) < 2 { return errors.Errorf("image name and at least one new name must be specified") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 0b7da64a8..2e0a22d92 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -7,8 +7,8 @@ import ( "text/tabwriter" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -60,7 +60,6 @@ func init() { } func topCmd(c *cliconfig.TopValues) error { - var container *libpod.Container var err error args := c.InputArgs @@ -77,37 +76,16 @@ func topCmd(c *cliconfig.TopValues) error { return errors.Errorf("you must provide the name or id of a running container") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - var descriptors []string - if c.Latest { - descriptors = args - container, err = runtime.GetLatestContainer() - } else { - descriptors = args[1:] - container, err = runtime.LookupContainer(args[0]) - } - - if err != nil { - return errors.Wrapf(err, "unable to lookup requested container") - } - - conStat, err := container.State() - if err != nil { - return errors.Wrapf(err, "unable to look up state for %s", args[0]) - } - if conStat != libpod.ContainerStateRunning { - return errors.Errorf("top can only be used on running containers") - } - psOutput, err := container.GetContainerPidInformation(descriptors) + psOutput, err := runtime.Top(c) if err != nil { return err } - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) for _, proc := range psOutput { fmt.Fprintln(w, proc) diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go index f205c83e4..6490c609d 100644 --- a/cmd/podman/tree.go +++ b/cmd/podman/tree.go @@ -51,7 +51,7 @@ func treeCmd(c *cliconfig.TreeValues) error { return errors.Errorf("you must provide at most 1 argument") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go index 580331673..b615f6266 100644 --- a/cmd/podman/trust_set_show.go +++ b/cmd/podman/trust_set_show.go @@ -74,7 +74,7 @@ File(s) must exist before using this command`) } func showTrustCmd(c *cliconfig.ShowTrustValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } @@ -131,7 +131,7 @@ func showTrustCmd(c *cliconfig.ShowTrustValues) error { } func setTrustCmd(c *cliconfig.SetTrustValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index cdf8b951a..ddbd00bd5 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -48,7 +48,7 @@ func init() { } func umountCmd(c *cliconfig.UmountValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating runtime") } diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index fa946bfd7..2cd6846fe 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -42,7 +42,7 @@ func unpauseCmd(c *cliconfig.UnpauseValues) error { return errors.New("unpause is not supported for rootless containers") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go index 787ad01cd..978678a84 100644 --- a/cmd/podman/varlink.go +++ b/cmd/podman/varlink.go @@ -55,7 +55,7 @@ func varlinkCmd(c *cliconfig.VarlinkValues) error { timeout := time.Duration(c.Timeout) * time.Millisecond // Create a single runtime for varlink - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 1fde72164..309f9765a 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -346,6 +346,7 @@ type Create ( publishAll: ?bool, quiet: ?bool, readonly: ?bool, + readonlytmpfs: ?bool, restart: ?string, rm: ?bool, rootfs: ?bool, @@ -524,6 +525,8 @@ method Ps(opts: PsOpts) -> (containers: []PsContainer) method GetContainersByStatus(status: []string) -> (containerS: []Container) +method Top (nameOrID: string, descriptors: []string) -> (top: []string) + # GetContainer returns information about a single container. If a container # with the given id doesn't exist, a [ContainerNotFound](#ContainerNotFound) # error will be returned. See also [ListContainers](ListContainers) and diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 2b10adb2b..84f6bba94 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -42,7 +42,7 @@ func init() { } func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go index 66d394307..e4b05f96a 100644 --- a/cmd/podman/volume_inspect.go +++ b/cmd/podman/volume_inspect.go @@ -43,7 +43,7 @@ func volumeInspectCmd(c *cliconfig.VolumeInspectValues) error { return errors.New("provide one or more volume names or use --all") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go index b9ab89196..581e595cb 100644 --- a/cmd/podman/volume_ls.go +++ b/cmd/podman/volume_ls.go @@ -72,7 +72,7 @@ func init() { } func volumeLsCmd(c *cliconfig.VolumeLsValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index ad62bfc22..6dc9e2403 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -63,7 +63,7 @@ func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { } func volumePruneCmd(c *cliconfig.VolumePruneValues) error { - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go index 4534019c6..77137eb7a 100644 --- a/cmd/podman/volume_rm.go +++ b/cmd/podman/volume_rm.go @@ -47,7 +47,7 @@ func volumeRmCmd(c *cliconfig.VolumeRmValues) error { return errors.New("choose either one or more volumes or all") } - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go index 97ec75b0c..380e861ed 100644 --- a/cmd/podman/wait.go +++ b/cmd/podman/wait.go @@ -51,7 +51,7 @@ func waitCmd(c *cliconfig.WaitValues) error { } interval := time.Duration(c.Interval) * time.Millisecond - runtime, err := adapter.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating runtime") } diff --git a/completions/bash/podman b/completions/bash/podman index dce23df2b..6acdcc05a 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1765,6 +1765,7 @@ _podman_container_run() { --publish-all -P --quiet --read-only + --read-only-tmpfs --tty -t " diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 24f1cce21..e84566ce3 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -49,7 +49,7 @@ ooe.sh sudo -E apt-get -qq install \ gettext \ go-md2man \ golang \ - iproute \ + iproute2 \ iptables \ libaio-dev \ libapparmor-dev \ @@ -68,6 +68,7 @@ ooe.sh sudo -E apt-get -qq install \ libprotobuf-dev \ libseccomp-dev \ libseccomp2 \ + libsystemd-dev \ libtool \ libudev-dev \ lsof \ diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index f61deebd2..52c965293 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -542,6 +542,9 @@ By default a container will have its root filesystem writable allowing processes to write files anywhere. By specifying the `--read-only` flag the container will have its root filesystem mounted as read only prohibiting any writes. +**--read-only-tmpfs**=*true*|*false* +If container is running in --read-only mode, then mount a read-write tmpfs on /run, /tmp, and /var/tmp. The default is *true* + **--restart=""** Not implemented. diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md index 40f7e8457..da142c0fb 100644 --- a/docs/podman-events.1.md +++ b/docs/podman-events.1.md @@ -9,7 +9,9 @@ podman\-events - Monitor Podman events ## DESCRIPTION Monitor and print events that occur in Podman. Each event will include a timestamp, -a type, a status, name (if applicable), and image (if applicable). +a type, a status, name (if applicable), and image (if applicable). The default logging +mechanism is *journald*. This can be changed in libpod.conf by changing the `events_logger` +value to `file`. Only `file` and `journald` are the accepted. The *container* event type will report the follow statuses: * attach diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 5a311980f..4411aca9e 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -415,6 +415,36 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. +**--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]* + +Attach a filesystem mount to the container + +Current supported mount TYPES are bind, and tmpfs. + + e.g. + + type=bind,source=/path/on/host,destination=/path/in/container + + type=tmpfs,tmpfs-size=512M,destination=/path/in/container + + Common Options: + + · src, source: mount source spec for bind and volume. Mandatory for bind. + + · dst, destination, target: mount destination spec. + + · ro, read-only: true or false (default). + + Options specific to bind: + + · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). + + Options specific to tmpfs: + + · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. + + · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. + **--name**="" Assign a name to the container @@ -534,6 +564,9 @@ By default a container will have its root filesystem writable allowing processes to write files anywhere. By specifying the `--read-only` flag the container will have its root filesystem mounted as read only prohibiting any writes. +**--read-only-tmpfs**=*true*|*false* +If container is running in --read-only mode, then mount a read-write tmpfs on /run, /tmp, and /var/tmp. The default is *true* + **--restart=""** Not implemented. @@ -708,36 +741,6 @@ Set the UTS mode for the container **NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]* - -Attach a filesystem mount to the container - -Current supported mount TYPES are bind, and tmpfs. - - e.g. - - type=bind,source=/path/on/host,destination=/path/in/container - - type=tmpfs,tmpfs-size=512M,destination=/path/in/container - - Common Options: - - · src, source: mount source spec for bind and volume. Mandatory for bind. - - · dst, destination, target: mount destination spec. - - · ro, read-only: true or false (default). - - Options specific to bind: - - · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). - - Options specific to tmpfs: - - · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. - - · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. - **--userns**="" Set the user namespace mode for the container. The use of userns is disabled by default. @@ -905,7 +908,11 @@ still need to write temporary data. The best way to handle this is to mount tmpfs directories on /run and /tmp. ``` -$ podman run --read-only --tmpfs /run --tmpfs /tmp -i -t fedora /bin/bash +$ podman run --read-only -i -t fedora /bin/bash +``` + +``` +$ podman run --read-only --read-only-tmpfs=false --tmpfs /run -i -t fedora /bin/bash ``` ### Exposing log messages from the container to the host's log diff --git a/docs/podman-system-migrate.1.md b/docs/podman-system-migrate.1.md new file mode 100644 index 000000000..7c2d1823c --- /dev/null +++ b/docs/podman-system-migrate.1.md @@ -0,0 +1,21 @@ +% podman-system-migrate(1) podman + +## NAME +podman\-system\-migrate - Migrate container to the latest version of podman + +## SYNOPSIS +** podman system migrate** + +## DESCRIPTION +** podman system migrate** migrates containers to the latest podman version. + +**podman system migrate** takes care of migrating existing containers to the latest version of podman if any change is necessary. + +## SYNOPSIS +**podman system migrate** + +## SEE ALSO +`podman(1)`, `libpod.conf(5)` + +# HISTORY +April 2019, Originally compiled by Giuseppe Scrivano (gscrivan at redhat dot com) diff --git a/docs/podman-system.1.md b/docs/podman-system.1.md index 32b3efdd9..d36715feb 100644 --- a/docs/podman-system.1.md +++ b/docs/podman-system.1.md @@ -17,6 +17,7 @@ The system command allows you to manage the podman systems | info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | | prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused data | | renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks. | +| migrate | [podman-system-migrate(1)](podman-system-migrate.1.md)| Migrate existing containers to a new podman version. | ## SEE ALSO podman(1) diff --git a/libpod.conf b/libpod.conf index 80422e3dd..ca8d0fb36 100644 --- a/libpod.conf +++ b/libpod.conf @@ -113,3 +113,7 @@ runc = [ "/bin/runc", "/usr/lib/cri-o-runc/sbin/runc" ] + +# Selects which logging mechanism to use for Podman events. Valid values +# are `journald` or `file`. +events_logger = "journald" diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index f352b188e..c5e404155 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -420,7 +420,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // It also expects to be able to write to /sys/fs/cgroup/systemd and /var/log/journal func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) error { options := []string{"rw", "rprivate", "noexec", "nosuid", "nodev"} - for _, dest := range []string{"/run", "/run/lock"} { + for _, dest := range []string{"/run"} { if MountExists(mounts, dest) { continue } diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 9b0f156b5..b370495fe 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -7,8 +7,22 @@ import ( "strings" "github.com/containers/psgo" + "github.com/pkg/errors" ) +// Top gathers statistics about the running processes in a container. It returns a +// []string for output +func (c *Container) Top(descriptors []string) ([]string, error) { + conStat, err := c.State() + if err != nil { + return nil, errors.Wrapf(err, "unable to look up state for %s", c.ID()) + } + if conStat != ContainerStateRunning { + return nil, errors.Errorf("top can only be used on running containers") + } + return c.GetContainerPidInformation(descriptors) +} + // GetContainerPidInformation returns process-related data of all processes in // the container. The output data can be controlled via the `descriptors` // argument which expects format descriptors and supports all AIXformat diff --git a/libpod/events.go b/libpod/events.go index b6a277789..13bb5bdde 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -1,14 +1,19 @@ package libpod import ( - "os" - "github.com/containers/libpod/libpod/events" - "github.com/hpcloud/tail" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// newEventer returns an eventer that can be used to read/write events +func (r *Runtime) newEventer() (events.Eventer, error) { + options := events.EventerOptions{ + EventerType: r.config.EventsLogger, + LogFilePath: r.config.EventsLogFilePath, + } + return events.NewEventer(options) +} + // newContainerEvent creates a new event based on a container func (c *Container) newContainerEvent(status events.Status) { e := events.NewEvent(status) @@ -16,8 +21,8 @@ func (c *Container) newContainerEvent(status events.Status) { e.Name = c.Name() e.Image = c.config.RootfsImageName e.Type = events.Container - if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) } } @@ -29,8 +34,8 @@ func (c *Container) newContainerExitedEvent(exitCode int32) { e.Image = c.config.RootfsImageName e.Type = events.Container e.ContainerExitCode = int(exitCode) - if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) } } @@ -40,8 +45,18 @@ func (p *Pod) newPodEvent(status events.Status) { e.ID = p.ID() e.Name = p.Name() e.Type = events.Pod - if err := e.Write(p.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", p.runtime.config.EventsLogFilePath) + if err := p.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write pod event: %q", err) + } +} + +// newSystemEvent creates a new event for libpod as a whole. +func (r *Runtime) newSystemEvent(status events.Status) { + e := events.NewEvent(status) + e.Type = events.System + + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("unable to write system event: %q", err) } } @@ -50,51 +65,17 @@ func (v *Volume) newVolumeEvent(status events.Status) { e := events.NewEvent(status) e.Name = v.Name() e.Type = events.Volume - if err := e.Write(v.runtime.config.EventsLogFilePath); err != nil { - logrus.Errorf("unable to write event to %s", v.runtime.config.EventsLogFilePath) + if err := v.runtime.eventer.Write(e); err != nil { + logrus.Errorf("unable to write volume event: %q", err) } } // Events is a wrapper function for everyone to begin tailing the events log // with options -func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, eventChannel chan *events.Event) error { - if !r.valid { - return ErrRuntimeStopped - } - - t, err := r.getTail(fromStart, stream) +func (r *Runtime) Events(options events.ReadOptions) error { + eventer, err := r.newEventer() if err != nil { return err } - for line := range t.Lines { - event, err := events.NewEventFromString(line.Text) - if err != nil { - return err - } - switch event.Type { - case events.Image, events.Volume, events.Pod, events.Container: - // no-op - default: - return errors.Errorf("event type %s is not valid in %s", event.Type.String(), r.config.EventsLogFilePath) - } - include := true - for _, filter := range options { - include = include && filter(event) - } - if include { - eventChannel <- event - } - } - close(eventChannel) - return nil -} - -func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) { - reopen := true - seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} - if fromStart || !stream { - seek.Whence = 0 - reopen = false - } - return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) + return eventer.Read(options) } diff --git a/libpod/events/config.go b/libpod/events/config.go new file mode 100644 index 000000000..36387e835 --- /dev/null +++ b/libpod/events/config.go @@ -0,0 +1,158 @@ +package events + +import ( + "time" +) + +// EventerType ... +type EventerType int + +const ( + // LogFile indicates the event logger will be a logfile + LogFile EventerType = iota + // Journald indicates journald should be used to log events + Journald EventerType = iota +) + +// Event describes the attributes of a libpod event +type Event struct { + // ContainerExitCode is for storing the exit code of a container which can + // be used for "internal" event notification + ContainerExitCode int + // ID can be for the container, image, volume, etc + ID string + // Image used where applicable + Image string + // Name where applicable + Name string + // Status describes the event that occurred + Status Status + // Time the event occurred + Time time.Time + // Type of event that occurred + Type Type +} + +// EventerOptions describe options that need to be passed to create +// an eventer +type EventerOptions struct { + // EventerType describes whether to use journald or a file + EventerType string + // LogFilePath is the path to where the log file should reside if using + // the file logger + LogFilePath string +} + +// Eventer is the interface for journald or file event logging +type Eventer interface { + // Write an event to a backend + Write(event Event) error + // Read an event from the backend + Read(options ReadOptions) error +} + +// ReadOptions describe the attributes needed to read event logs +type ReadOptions struct { + // EventChannel is the comm path back to user + EventChannel chan *Event + // Filters are key/value pairs that describe to limit output + Filters []string + // FromStart means you start reading from the start of the logs + FromStart bool + // Since reads "since" the given time + Since string + // Stream is follow + Stream bool + // Until reads "until" the given time + Until string +} + +// Type of event that occurred (container, volume, image, pod, etc) +type Type string + +// Status describes the actual event action (stop, start, create, kill) +type Status string + +const ( + // If you add or subtract any values to the following lists, make sure you also update + // the switch statements below and the enums for EventType or EventStatus in the + // varlink description file. + + // Container - event is related to containers + Container Type = "container" + // Image - event is related to images + Image Type = "image" + // Pod - event is related to pods + Pod Type = "pod" + // System - event is related to Podman whole and not to any specific + // container/pod/image/volume + System Type = "system" + // Volume - event is related to volumes + Volume Type = "volume" + + // Attach ... + Attach Status = "attach" + // Checkpoint ... + Checkpoint Status = "checkpoint" + // Cleanup ... + Cleanup Status = "cleanup" + // Commit ... + Commit Status = "commit" + // Create ... + Create Status = "create" + // Exec ... + Exec Status = "exec" + // Exited indicates that a container's process died + Exited Status = "died" + // Export ... + Export Status = "export" + // History ... + History Status = "history" + // Import ... + Import Status = "import" + // Init ... + Init Status = "init" + // Kill ... + Kill Status = "kill" + // LoadFromArchive ... + LoadFromArchive Status = "loadfromarchive" + // Mount ... + Mount Status = "mount" + // Pause ... + Pause Status = "pause" + // Prune ... + Prune Status = "prune" + // Pull ... + Pull Status = "pull" + // Push ... + Push Status = "push" + // Refresh indicates that the system refreshed the state after a + // reboot. + Refresh Status = "refresh" + // Remove ... + Remove Status = "remove" + // Renumber indicates that lock numbers were reallocated at user + // request. + Renumber Status = "renumber" + // Restore ... + Restore Status = "restore" + // Save ... + Save Status = "save" + // Start ... + Start Status = "start" + // Stop ... + Stop Status = "stop" + // Sync ... + Sync Status = "sync" + // Tag ... + Tag Status = "tag" + // Unmount ... + Unmount Status = "unmount" + // Unpause ... + Unpause Status = "unpause" + // Untag ... + Untag Status = "untag" +) + +// EventFilter for filtering events +type EventFilter func(*Event) bool diff --git a/libpod/events/events.go b/libpod/events/events.go index 074a3ba5b..202c9db4e 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -6,109 +6,18 @@ import ( "os" "time" - "github.com/containers/storage" + "github.com/hpcloud/tail" "github.com/pkg/errors" ) -// Event describes the attributes of a libpod event -type Event struct { - // ContainerExitCode is for storing the exit code of a container which can - // be used for "internal" event notification - ContainerExitCode int - // ID can be for the container, image, volume, etc - ID string - // Image used where applicable - Image string - // Name where applicable - Name string - // Status describes the event that occurred - Status Status - // Time the event occurred - Time time.Time - // Type of event that occurred - Type Type -} - -// Type of event that occurred (container, volume, image, pod, etc) -type Type string - -// Status describes the actual event action (stop, start, create, kill) -type Status string +// String returns a string representation of EventerType +func (et EventerType) String() string { + if et == LogFile { + return "file" -const ( - // If you add or subtract any values to the following lists, make sure you also update - // the switch statements below and the enums for EventType or EventStatus in the - // varlink description file. - - // Container - event is related to containers - Container Type = "container" - // Image - event is related to images - Image Type = "image" - // Pod - event is related to pods - Pod Type = "pod" - // Volume - event is related to volumes - Volume Type = "volume" - - // Attach ... - Attach Status = "attach" - // Checkpoint ... - Checkpoint Status = "checkpoint" - // Cleanup ... - Cleanup Status = "cleanup" - // Commit ... - Commit Status = "commit" - // Create ... - Create Status = "create" - // Exec ... - Exec Status = "exec" - // Exited indicates that a container's process died - Exited Status = "died" - // Export ... - Export Status = "export" - // History ... - History Status = "history" - // Import ... - Import Status = "import" - // Init ... - Init Status = "init" - // Kill ... - Kill Status = "kill" - // LoadFromArchive ... - LoadFromArchive Status = "status" - // Mount ... - Mount Status = "mount" - // Pause ... - Pause Status = "pause" - // Prune ... - Prune Status = "prune" - // Pull ... - Pull Status = "pull" - // Push ... - Push Status = "push" - // Remove ... - Remove Status = "remove" - // Restore ... - Restore Status = "restore" - // Save ... - Save Status = "save" - // Start ... - Start Status = "start" - // Stop ... - Stop Status = "stop" - // Sync ... - Sync Status = "sync" - // Tag ... - Tag Status = "tag" - // Unmount ... - Unmount Status = "unmount" - // Unpause ... - Unpause Status = "unpause" - // Untag ... - Untag Status = "untag" -) - -// EventFilter for filtering events -type EventFilter func(*Event) bool + } + return "journald" +} // NewEvent creates a event struct and populates with // the given status and time. @@ -119,30 +28,6 @@ func NewEvent(status Status) Event { } } -// Write will record the event to the given path -func (e *Event) Write(path string) error { - // We need to lock events file - lock, err := storage.GetLockfile(path + ".lock") - if err != nil { - return err - } - lock.Lock() - defer lock.Unlock() - f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) - if err != nil { - return err - } - defer f.Close() - eventJSONString, err := e.ToJSONString() - if err != nil { - return err - } - if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { - return err - } - return nil -} - // Recycle checks if the event log has reach a limit and if so // renames the current log and starts a new one. The remove bool // indicates the old log file should be deleted. @@ -164,6 +49,8 @@ func (e *Event) ToHumanReadable() string { humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.Image, e.Name) case Image: humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name) + case System: + humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status) case Volume: humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) } @@ -172,7 +59,7 @@ func (e *Event) ToHumanReadable() string { // NewEventFromString takes stringified json and converts // it to an event -func NewEventFromString(event string) (*Event, error) { +func newEventFromJSONString(event string) (*Event, error) { e := Event{} if err := json.Unmarshal([]byte(event), &e); err != nil { return nil, err @@ -200,10 +87,12 @@ func StringToType(name string) (Type, error) { return Image, nil case Pod.String(): return Pod, nil + case System.String(): + return System, nil case Volume.String(): return Volume, nil } - return "", errors.Errorf("unknown event type %s", name) + return "", errors.Errorf("unknown event type %q", name) } // StringToStatus converts a string to an Event Status @@ -249,8 +138,14 @@ func StringToStatus(name string) (Status, error) { return Pull, nil case Push.String(): return Push, nil + case Refresh.String(): + return Refresh, nil case Remove.String(): return Remove, nil + case Renumber.String(): + return Renumber, nil + case Restore.String(): + return Restore, nil case Save.String(): return Save, nil case Start.String(): @@ -268,5 +163,19 @@ func StringToStatus(name string) (Status, error) { case Untag.String(): return Untag, nil } - return "", errors.Errorf("unknown event status %s", name) + return "", errors.Errorf("unknown event status %q", name) +} + +func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { + reopen := true + seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} + if options.FromStart || !options.Stream { + seek.Whence = 0 + reopen = false + } + stream := options.Stream + if len(options.Until) > 0 { + stream = false + } + return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) } diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go new file mode 100644 index 000000000..da5d7965e --- /dev/null +++ b/libpod/events/events_linux.go @@ -0,0 +1,23 @@ +package events + +import ( + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// NewEventer creates an eventer based on the eventer type +func NewEventer(options EventerOptions) (Eventer, error) { + var eventer Eventer + logrus.Debugf("Initializing event backend %s", options.EventerType) + switch strings.ToUpper(options.EventerType) { + case strings.ToUpper(Journald.String()): + eventer = EventJournalD{options} + case strings.ToUpper(LogFile.String()): + eventer = EventLogFile{options} + default: + return eventer, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType)) + } + return eventer, nil +} diff --git a/libpod/events/events_unsupported.go b/libpod/events/events_unsupported.go new file mode 100644 index 000000000..5b32a1b4b --- /dev/null +++ b/libpod/events/events_unsupported.go @@ -0,0 +1,10 @@ +// +build !linux + +package events + +import "github.com/pkg/errors" + +// NewEventer creates an eventer based on the eventer type +func NewEventer(options EventerOptions) (Eventer, error) { + return nil, errors.New("this function is not available for your platform") +} diff --git a/cmd/podman/shared/events.go b/libpod/events/filters.go index c62044271..9a64082d1 100644 --- a/cmd/podman/shared/events.go +++ b/libpod/events/filters.go @@ -1,20 +1,19 @@ -package shared +package events import ( "fmt" "strings" "time" - "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) -func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool, error) { +func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error) { switch strings.ToUpper(filter) { case "CONTAINER": - return func(e *events.Event) bool { - if e.Type != events.Container { + return func(e *Event) bool { + if e.Type != Container { return false } if e.Name == filterValue { @@ -23,12 +22,12 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool return strings.HasPrefix(e.ID, filterValue) }, nil case "EVENT", "STATUS": - return func(e *events.Event) bool { + return func(e *Event) bool { return fmt.Sprintf("%s", e.Status) == filterValue }, nil case "IMAGE": - return func(e *events.Event) bool { - if e.Type != events.Image { + return func(e *Event) bool { + if e.Type != Image { return false } if e.Name == filterValue { @@ -37,8 +36,8 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool return strings.HasPrefix(e.ID, filterValue) }, nil case "POD": - return func(e *events.Event) bool { - if e.Type != events.Pod { + return func(e *Event) bool { + if e.Type != Pod { return false } if e.Name == filterValue { @@ -47,28 +46,28 @@ func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool return strings.HasPrefix(e.ID, filterValue) }, nil case "VOLUME": - return func(e *events.Event) bool { - if e.Type != events.Volume { + return func(e *Event) bool { + if e.Type != Volume { return false } return strings.HasPrefix(e.ID, filterValue) }, nil case "TYPE": - return func(e *events.Event) bool { + return func(e *Event) bool { return fmt.Sprintf("%s", e.Type) == filterValue }, nil } return nil, errors.Errorf("%s is an invalid filter", filter) } -func generateEventSinceOption(timeSince time.Time) func(e *events.Event) bool { - return func(e *events.Event) bool { +func generateEventSinceOption(timeSince time.Time) func(e *Event) bool { + return func(e *Event) bool { return e.Time.After(timeSince) } } -func generateEventUntilOption(timeUntil time.Time) func(e *events.Event) bool { - return func(e *events.Event) bool { +func generateEventUntilOption(timeUntil time.Time) func(e *Event) bool { + return func(e *Event) bool { return e.Time.Before(timeUntil) } @@ -82,8 +81,8 @@ func parseFilter(filter string) (string, string, error) { return filterSplit[0], filterSplit[1], nil } -func GenerateEventOptions(filters []string, since, until string) ([]events.EventFilter, error) { - var options []events.EventFilter +func generateEventOptions(filters []string, since, until string) ([]EventFilter, error) { + var options []EventFilter for _, filter := range filters { key, val, err := parseFilter(filter) if err != nil { diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go new file mode 100644 index 000000000..8ba5bc2c7 --- /dev/null +++ b/libpod/events/journal_linux.go @@ -0,0 +1,136 @@ +package events + +import ( + "fmt" + "time" + + "github.com/coreos/go-systemd/journal" + "github.com/coreos/go-systemd/sdjournal" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// EventJournalD is the journald implementation of an eventer +type EventJournalD struct { + options EventerOptions +} + +// Write to journald +func (e EventJournalD) Write(ee Event) error { + m := make(map[string]string) + m["SYSLOG_IDENTIFIER"] = "podman" + m["PODMAN_EVENT"] = ee.Status.String() + m["PODMAN_TYPE"] = ee.Type.String() + m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano) + + // Add specialized information based on the podman type + switch ee.Type { + case Image: + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + case Container, Pod: + m["PODMAN_IMAGE"] = ee.Image + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + case Volume: + m["PODMAN_NAME"] = ee.Name + } + return journal.Send(fmt.Sprintf("%s", ee.ToHumanReadable()), journal.PriInfo, m) +} + +// Read reads events from the journal and sends qualified events to the event channel +func (e EventJournalD) Read(options ReadOptions) error { + eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until) + if err != nil { + return errors.Wrapf(err, "failed to generate event options") + } + podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint + j, err := sdjournal.NewJournal() //nolint + if err != nil { + return err + } + if err := j.AddMatch(podmanJournal.String()); err != nil { + return errors.Wrap(err, "failed to add filter for event log") + } + if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream { + if err := j.SeekTail(); err != nil { + return errors.Wrap(err, "failed to seek end of journal") + } + } + // the api requires a next|prev before getting a cursor + if _, err := j.Next(); err != nil { + return err + } + prevCursor, err := j.GetCursor() + if err != nil { + return err + } + defer close(options.EventChannel) + for { + if _, err := j.Next(); err != nil { + return err + } + newCursor, err := j.GetCursor() + if err != nil { + return err + } + if prevCursor == newCursor { + if len(options.Until) > 0 || !options.Stream { + break + } + _ = j.Wait(sdjournal.IndefiniteWait) //nolint + continue + } + prevCursor = newCursor + entry, err := j.GetEntry() + if err != nil { + return err + } + newEvent, err := newEventFromJournalEntry(entry) + if err != nil { + // We can't decode this event. + // Don't fail hard - that would make events unusable. + // Instead, log and continue. + logrus.Errorf("Unable to decode event: %v", err) + continue + } + include := true + for _, filter := range eventOptions { + include = include && filter(newEvent) + } + if include { + options.EventChannel <- newEvent + } + } + return nil + +} + +func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint + newEvent := Event{} + eventType, err := StringToType(entry.Fields["PODMAN_TYPE"]) + if err != nil { + return nil, err + } + eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"]) + if err != nil { + return nil, err + } + eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"]) + if err != nil { + return nil, err + } + newEvent.Type = eventType + newEvent.Time = eventTime + newEvent.Status = eventStatus + newEvent.Name = entry.Fields["PODMAN_NAME"] + + switch eventType { + case Container, Pod: + newEvent.ID = entry.Fields["PODMAN_ID"] + newEvent.Image = entry.Fields["PODMAN_IMAGE"] + case Image: + newEvent.ID = entry.Fields["PODMAN_ID"] + } + return &newEvent, nil +} diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go new file mode 100644 index 000000000..e5efc09bb --- /dev/null +++ b/libpod/events/logfile.go @@ -0,0 +1,73 @@ +package events + +import ( + "fmt" + "os" + + "github.com/containers/storage" + "github.com/pkg/errors" +) + +// EventLogFile is the structure for event writing to a logfile. It contains the eventer +// options and the event itself. Methods for reading and writing are also defined from it. +type EventLogFile struct { + options EventerOptions +} + +// Writes to the log file +func (e EventLogFile) Write(ee Event) error { + // We need to lock events file + lock, err := storage.GetLockfile(e.options.LogFilePath + ".lock") + if err != nil { + return err + } + lock.Lock() + defer lock.Unlock() + f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) + if err != nil { + return err + } + defer f.Close() + eventJSONString, err := ee.ToJSONString() + if err != nil { + return err + } + if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { + return err + } + return nil + +} + +// Reads from the log file +func (e EventLogFile) Read(options ReadOptions) error { + eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until) + if err != nil { + return errors.Wrapf(err, "unable to generate event options") + } + t, err := e.getTail(options) + if err != nil { + return err + } + for line := range t.Lines { + event, err := newEventFromJSONString(line.Text) + if err != nil { + return err + } + switch event.Type { + case Image, Volume, Pod, Container: + // no-op + default: + return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath) + } + include := true + for _, filter := range eventOptions { + include = include && filter(event) + } + if include { + options.EventChannel <- event + } + } + close(options.EventChannel) + return nil +} diff --git a/libpod/events/nullout.go b/libpod/events/nullout.go new file mode 100644 index 000000000..7d811a9c7 --- /dev/null +++ b/libpod/events/nullout.go @@ -0,0 +1,23 @@ +package events + +// EventToNull is an eventer type that only performs write operations +// and only writes to /dev/null. It is meant for unittests only +type EventToNull struct{} + +// Write eats the event and always returns nil +func (e EventToNull) Write(ee Event) error { + return nil +} + +// Read does nothing. Do not use it. +func (e EventToNull) Read(options ReadOptions) error { + return nil +} + +// NewNullEventer returns a new null eventer. You should only do this for +// the purposes on internal libpod testing. +func NewNullEventer() Eventer { + var e Eventer + e = EventToNull{} + return e +} diff --git a/libpod/image/image.go b/libpod/image/image.go index 757d034a2..b965a4640 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -66,6 +66,8 @@ type Runtime struct { store storage.Store SignaturePolicyPath string EventsLogFilePath string + EventsLogger string + Eventer events.Eventer } // InfoImage keep information of Image along with all associated layers @@ -353,8 +355,8 @@ func (i *Image) TopLayer() string { // outside the context of images // TODO: the force param does nothing as of now. Need to move container // handling logic here eventually. -func (i *Image) Remove(force bool) error { - parent, err := i.GetParent() +func (i *Image) Remove(ctx context.Context, force bool) error { + parent, err := i.GetParent(ctx) if err != nil { return err } @@ -363,11 +365,11 @@ func (i *Image) Remove(force bool) error { } i.newImageEvent(events.Remove) for parent != nil { - nextParent, err := parent.GetParent() + nextParent, err := parent.GetParent(ctx) if err != nil { return err } - children, err := parent.GetChildren() + children, err := parent.GetChildren(ctx) if err != nil { return err } @@ -679,7 +681,8 @@ type History struct { Comment string `json:"comment"` } -// History gets the history of an image and information about its layers +// History gets the history of an image and the IDs of images that are part of +// its history func (i *Image) History(ctx context.Context) ([]*History, error) { img, err := i.toImageRef(ctx) if err != nil { @@ -690,31 +693,92 @@ func (i *Image) History(ctx context.Context) ([]*History, error) { return nil, err } - // Get the IDs of the images making up the history layers - // if the images exist locally in the store + // Use our layers list to find images that use one of them as its + // topmost layer. + interestingLayers := make(map[string]bool) + layer, err := i.imageruntime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + for layer != nil { + interestingLayers[layer.ID] = true + if layer.Parent == "" { + break + } + layer, err = i.imageruntime.store.Layer(layer.Parent) + if err != nil { + return nil, err + } + } + + // Get the IDs of the images that share some of our layers. Hopefully + // this step means that we'll be able to avoid reading the + // configuration of every single image in local storage later on. images, err := i.imageruntime.GetImages() if err != nil { return nil, errors.Wrapf(err, "error getting images from store") } - imageIDs := []string{i.ID()} - if err := i.historyLayerIDs(i.TopLayer(), images, &imageIDs); err != nil { - return nil, errors.Wrap(err, "error getting image IDs for layers in history") + interestingImages := make([]*Image, 0, len(images)) + for i := range images { + if interestingLayers[images[i].TopLayer()] { + interestingImages = append(interestingImages, images[i]) + } + } + + // Build a list of image IDs that correspond to our history entries. + historyImages := make([]*Image, len(oci.History)) + if len(oci.History) > 0 { + // The starting image shares its whole history with itself. + historyImages[len(historyImages)-1] = i + for i := range interestingImages { + image, err := images[i].ociv1Image(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error getting image configuration for image %q", images[i].ID()) + } + // If the candidate has a longer history or no history + // at all, then it doesn't share the portion of our + // history that we're interested in matching with other + // images. + if len(image.History) == 0 || len(image.History) > len(historyImages) { + continue + } + // If we don't include all of the layers that the + // candidate image does (i.e., our rootfs didn't look + // like its rootfs at any point), then it can't be part + // of our history. + if len(image.RootFS.DiffIDs) > len(oci.RootFS.DiffIDs) { + continue + } + candidateLayersAreUsed := true + for i := range image.RootFS.DiffIDs { + if image.RootFS.DiffIDs[i] != oci.RootFS.DiffIDs[i] { + candidateLayersAreUsed = false + break + } + } + if !candidateLayersAreUsed { + continue + } + // If the candidate's entire history is an initial + // portion of our history, then we're based on it, + // either directly or indirectly. + sharedHistory := historiesMatch(oci.History, image.History) + if sharedHistory == len(image.History) { + historyImages[sharedHistory-1] = images[i] + } + } } var ( - imageID string - imgIDCount = 0 size int64 sizeCount = 1 allHistory []*History ) for i := len(oci.History) - 1; i >= 0; i-- { - if imgIDCount < len(imageIDs) { - imageID = imageIDs[imgIDCount] - imgIDCount++ - } else { - imageID = "<missing>" + imageID := "<missing>" + if historyImages[i] != nil { + imageID = historyImages[i].ID() } if !oci.History[i].EmptyLayer { size = img.LayerInfos()[len(img.LayerInfos())-sizeCount].Size @@ -1006,26 +1070,110 @@ func splitString(input string) string { // IsParent goes through the layers in the store and checks if i.TopLayer is // the parent of any other layer in store. Double check that image with that // layer exists as well. -func (i *Image) IsParent() (bool, error) { - children, err := i.GetChildren() +func (i *Image) IsParent(ctx context.Context) (bool, error) { + children, err := i.getChildren(ctx, 1) if err != nil { return false, err } return len(children) > 0, nil } +// historiesMatch returns the number of entries in the histories which have the +// same contents +func historiesMatch(a, b []imgspecv1.History) int { + i := 0 + for i < len(a) && i < len(b) { + if a[i].Created != nil && b[i].Created == nil { + return i + } + if a[i].Created == nil && b[i].Created != nil { + return i + } + if a[i].Created != nil && b[i].Created != nil { + if !a[i].Created.Equal(*(b[i].Created)) { + return i + } + } + if a[i].CreatedBy != b[i].CreatedBy { + return i + } + if a[i].Author != b[i].Author { + return i + } + if a[i].Comment != b[i].Comment { + return i + } + if a[i].EmptyLayer != b[i].EmptyLayer { + return i + } + i++ + } + return i +} + +// areParentAndChild checks diff ID and history in the two images and return +// true if the second should be considered to be directly based on the first +func areParentAndChild(parent, child *imgspecv1.Image) bool { + // the child and candidate parent should share all of the + // candidate parent's diff IDs, which together would have + // controlled which layers were used + if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) { + return false + } + childUsesCandidateDiffs := true + for i := range parent.RootFS.DiffIDs { + if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] { + childUsesCandidateDiffs = false + break + } + } + if !childUsesCandidateDiffs { + return false + } + // the child should have the same history as the parent, plus + // one more entry + if len(parent.History)+1 != len(child.History) { + return false + } + if historiesMatch(parent.History, child.History) != len(parent.History) { + return false + } + return true +} + // GetParent returns the image ID of the parent. Return nil if a parent is not found. -func (i *Image) GetParent() (*Image, error) { +func (i *Image) GetParent(ctx context.Context) (*Image, error) { images, err := i.imageruntime.GetImages() if err != nil { return nil, err } - layer, err := i.imageruntime.store.Layer(i.TopLayer()) + childLayer, err := i.imageruntime.store.Layer(i.TopLayer()) + if err != nil { + return nil, err + } + // fetch the configuration for the child image + child, err := i.ociv1Image(ctx) if err != nil { return nil, err } for _, img := range images { - if img.TopLayer() == layer.Parent { + if img.ID() == i.ID() { + continue + } + candidateLayer := img.TopLayer() + // as a child, our top layer is either the candidate parent's + // layer, or one that's derived from it, so skip over any + // candidate image where we know that isn't the case + if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID { + continue + } + // fetch the configuration for the candidate image + candidate, err := img.ociv1Image(ctx) + if err != nil { + return nil, err + } + // compare them + if areParentAndChild(candidate, child) { return img, nil } } @@ -1033,36 +1181,53 @@ func (i *Image) GetParent() (*Image, error) { } // GetChildren returns a list of the imageIDs that depend on the image -func (i *Image) GetChildren() ([]string, error) { +func (i *Image) GetChildren(ctx context.Context) ([]string, error) { + return i.getChildren(ctx, 0) +} + +// getChildren returns a list of at most "max" imageIDs that depend on the image +func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) { var children []string images, err := i.imageruntime.GetImages() if err != nil { return nil, err } - layers, err := i.imageruntime.store.Layers() + + // fetch the configuration for the parent image + parent, err := i.ociv1Image(ctx) if err != nil { return nil, err } + parentLayer := i.TopLayer() - for _, layer := range layers { - if layer.Parent == i.TopLayer() { - if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 { - children = append(children, imageID...) - } - } - } - return children, nil -} - -// getImageOfTopLayer returns the image ID where layer is the top layer of the image -func getImageOfTopLayer(images []*Image, layer string) []string { - var matches []string for _, img := range images { - if img.TopLayer() == layer { - matches = append(matches, img.ID()) + if img.ID() == i.ID() { + continue + } + candidateLayer, err := img.Layer() + if err != nil { + return nil, err + } + // if this image's top layer is not our top layer, and is not + // based on our top layer, we can skip it + if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer { + continue + } + // fetch the configuration for the candidate image + candidate, err := img.ociv1Image(ctx) + if err != nil { + return nil, err + } + // compare them + if areParentAndChild(parent, candidate) { + children = append(children, img.ID()) + } + // if we're not building an exhaustive list, maybe we're done? + if max > 0 && len(children) >= max { + break } } - return matches + return children, nil } // InputIsID returns a bool if the user input for an image @@ -1203,7 +1368,7 @@ func (ir *Runtime) newImageEvent(status events.Status, name string) { e := events.NewEvent(status) e.Type = events.Image e.Name = name - if err := e.Write(ir.EventsLogFilePath); err != nil { + if err := ir.Eventer.Write(e); err != nil { logrus.Infof("unable to write event to %s", ir.EventsLogFilePath) } } @@ -1216,7 +1381,7 @@ func (i *Image) newImageEvent(status events.Status) { if len(i.Names()) > 0 { e.Name = i.Names()[0] } - if err := e.Write(i.imageruntime.EventsLogFilePath); err != nil { + if err := i.imageruntime.Eventer.Write(e); err != nil { logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath) } } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 075ba119d..e93ebf797 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -3,6 +3,7 @@ package image import ( "context" "fmt" + "github.com/containers/libpod/libpod/events" "io" "io/ioutil" "os" @@ -87,6 +88,7 @@ func TestImage_NewFromLocal(t *testing.T) { // Need images to be present for this test ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil) @@ -127,6 +129,7 @@ func TestImage_New(t *testing.T) { } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() // Build the list of pull names names = append(names, bbNames...) names = append(names, fedoraNames...) @@ -139,7 +142,7 @@ func TestImage_New(t *testing.T) { newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") - err = newImage.Remove(false) + err = newImage.Remove(context.Background(), false) assert.NoError(t, err) } @@ -164,6 +167,7 @@ func TestImage_MatchRepoTag(t *testing.T) { } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + ir.Eventer = events.NewNullEventer() newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil) assert.NoError(t, err) err = newImage.TagImage("foo:latest") diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 5bd3c2c99..a4f8a0c9f 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -1,6 +1,8 @@ package image import ( + "context" + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" ) @@ -34,14 +36,14 @@ func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) { // PruneImages prunes dangling and optionally all unused images from the local // image store -func (ir *Runtime) PruneImages(all bool) ([]string, error) { +func (ir *Runtime) PruneImages(ctx context.Context, all bool) ([]string, error) { var prunedCids []string pruneImages, err := ir.GetPruneImages(all) if err != nil { return nil, errors.Wrap(err, "unable to get images to prune") } for _, p := range pruneImages { - if err := p.Remove(true); err != nil { + if err := p.Remove(ctx, true); err != nil { return nil, errors.Wrap(err, "failed to prune image") } defer p.newImageEvent(events.Prune) diff --git a/libpod/options.go b/libpod/options.go index 8038f1935..9932d5453 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1,6 +1,7 @@ package libpod import ( + "context" "net" "os" "path/filepath" @@ -436,6 +437,22 @@ func WithRenumber() RuntimeOption { } } +// WithMigrate instructs libpod to perform a lock migrateing while +// initializing. This will handle migrations from early versions of libpod with +// file locks to newer versions with SHM locking, as well as changes in the +// number of configured locks. +func WithMigrate() RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + rt.doMigrate = true + + return nil + } +} + // Container Creation Options // WithShmDir sets the directory that should be mounted on /dev/shm. @@ -450,6 +467,19 @@ func WithShmDir(dir string) CtrCreateOption { } } +// WithContext sets the context to use. +func WithContext(ctx context.Context) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + rt.ctx = ctx + + return nil + } +} + // WithSystemd turns on systemd mode in the container func WithSystemd() CtrCreateOption { return func(ctr *Container) error { diff --git a/libpod/runtime.go b/libpod/runtime.go index 3b1c2be98..e85242028 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -1,6 +1,7 @@ package libpod import ( + "context" "fmt" "io/ioutil" "os" @@ -11,6 +12,7 @@ import ( "github.com/BurntSushi/toml" is "github.com/containers/image/storage" "github.com/containers/image/types" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/firewall" @@ -99,12 +101,19 @@ type Runtime struct { // unused. doRenumber bool + doMigrate bool + // valid indicates whether the runtime is ready to use. // valid is set to true when a runtime is returned from GetRuntime(), // and remains true until the runtime is shut down (rendering its // storage unusable). When valid is false, the runtime cannot be used. valid bool lock sync.RWMutex + + // mechanism to read and write even logs + eventer events.Eventer + + ctx context.Context } // OCIRuntimePath contains information about an OCI runtime. @@ -222,6 +231,8 @@ type RuntimeConfig struct { // pods. NumLocks uint32 `toml:"num_locks,omitempty"` + // EventsLogger determines where events should be logged + EventsLogger string `toml:"events_logger"` // EventsLogFilePath is where the events log is stored. EventsLogFilePath string `toml:-"events_logfile_path"` } @@ -252,7 +263,6 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { if err != nil { return RuntimeConfig{}, err } - return RuntimeConfig{ // Leave this empty so containers/storage will use its defaults StorageConfig: storage.StoreOptions{}, @@ -296,6 +306,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { EnablePortReservation: true, EnableLabeling: true, NumLocks: 2048, + EventsLogger: "journald", }, nil } @@ -748,6 +759,17 @@ func makeRuntime(runtime *Runtime) (err error) { if err != nil { return err } + + defer func() { + if err != nil && store != nil { + // Don't forcibly shut down + // We could be opening a store in use by another libpod + _, err2 := store.Shutdown(false) + if err2 != nil { + logrus.Errorf("Error removing store for partially-created runtime: %s", err2) + } + } + }() } runtime.store = store @@ -755,27 +777,24 @@ func makeRuntime(runtime *Runtime) (err error) { // Set up image runtime and store in runtime ir := image.NewImageRuntimeFromStore(runtime.store) - if err != nil { - return err - } runtime.imageRuntime = ir // Setting signaturepolicypath ir.SignaturePolicyPath = runtime.config.SignaturePolicyPath + // Set logfile path for events ir.EventsLogFilePath = runtime.config.EventsLogFilePath + // Set logger type + ir.EventsLogger = runtime.config.EventsLogger - defer func() { - if err != nil && store != nil { - // Don't forcibly shut down - // We could be opening a store in use by another libpod - _, err2 := store.Shutdown(false) - if err2 != nil { - logrus.Errorf("Error removing store for partially-created runtime: %s", err2) - } - } - }() + // Setup the eventer + eventer, err := runtime.newEventer() + if err != nil { + return err + } + runtime.eventer = eventer + ir.Eventer = eventer // Set up a storage service for creating container root filesystems from // images @@ -948,6 +967,24 @@ func makeRuntime(runtime *Runtime) (err error) { // further runtime.valid = true + if runtime.doMigrate { + if os.Geteuid() != 0 { + aliveLock.Unlock() + locked = false + + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + if err := runtime.migrate(); err != nil { + return err + } + } + return nil } @@ -1018,6 +1055,8 @@ func (r *Runtime) Shutdown(force bool) error { // Refreshes the state, recreating temporary files // Does not check validity as the runtime is not valid until after this has run func (r *Runtime) refresh(alivePath string) error { + logrus.Debugf("Podman detected system restart - performing state refresh") + // First clear the state in the database if err := r.state.Refresh(); err != nil { return err @@ -1058,6 +1097,8 @@ func (r *Runtime) refresh(alivePath string) error { } defer file.Close() + r.newSystemEvent(events.Refresh) + return nil } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 02f925fc6..5e9f65acc 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -57,7 +57,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) } } - hasChildren, err := img.IsParent() + hasChildren, err := img.IsParent(ctx) if err != nil { return "", err } @@ -82,12 +82,12 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) // reponames and no force is applied, we error out. return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID()) } - err = img.Remove(force) + err = img.Remove(ctx, force) if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer { if errStorage := r.rmStorageContainers(force, img); errStorage == nil { // Containers associated with the image should be deleted now, // let's try removing the image again. - err = img.Remove(force) + err = img.Remove(ctx, force) } else { err = errStorage } diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go new file mode 100644 index 000000000..a084df289 --- /dev/null +++ b/libpod/runtime_migrate.go @@ -0,0 +1,47 @@ +package libpod + +import ( + "path/filepath" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (r *Runtime) migrate() error { + runningContainers, err := r.GetRunningContainers() + if err != nil { + return err + } + + allCtrs, err := r.state.AllContainers() + if err != nil { + return err + } + + logrus.Infof("stopping all containers") + for _, ctr := range runningContainers { + logrus.Infof("stopping %s", ctr.ID()) + if err := ctr.Stop(); err != nil { + return errors.Wrapf(err, "cannot stop container %s", ctr.ID()) + } + } + + for _, ctr := range allCtrs { + oldLocation := filepath.Join(ctr.state.RunDir, "conmon.pid") + if ctr.config.ConmonPidFile == oldLocation { + logrus.Infof("changing conmon PID file for %s", ctr.ID()) + ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") + if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil { + return errors.Wrapf(err, "error rewriting config for container %s", ctr.ID()) + } + } + } + + for _, ctr := range runningContainers { + if err := ctr.Start(r.ctx, true); err != nil { + logrus.Errorf("error restarting container %s", ctr.ID()) + } + } + + return nil +} diff --git a/libpod/runtime_renumber.go b/libpod/runtime_renumber.go index 125cf0825..735ffba34 100644 --- a/libpod/runtime_renumber.go +++ b/libpod/runtime_renumber.go @@ -1,6 +1,7 @@ package libpod import ( + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" ) @@ -53,5 +54,7 @@ func (r *Runtime) renumberLocks() error { } } + r.newSystemEvent(events.Renumber) + return nil } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index a5b911da1..9ec897a60 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -697,3 +697,182 @@ func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.Unp } return pool.Run() } + +// Restart containers without or without a timeout +func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) ([]string, map[string]error, error) { + var ( + containers []*libpod.Container + restartContainers []*libpod.Container + err error + ) + useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed + inputTimeout := c.Timeout + + // Handle --latest + if c.Latest { + lastCtr, err := r.Runtime.GetLatestContainer() + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get latest container") + } + restartContainers = append(restartContainers, lastCtr) + } else if c.Running { + containers, err = r.GetRunningContainers() + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, containers...) + } else if c.All { + containers, err = r.Runtime.GetAllContainers() + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, containers...) + } else { + for _, id := range c.InputArgs { + ctr, err := r.Runtime.LookupContainer(id) + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, ctr) + } + } + + maxWorkers := shared.DefaultPoolSize("restart") + if c.GlobalIsSet("max-workers") { + maxWorkers = c.GlobalFlags.MaxWorks + } + + logrus.Debugf("Setting maximum workers to %d", maxWorkers) + + // We now have a slice of all the containers to be restarted. Iterate them to + // create restart Funcs with a timeout as needed + pool := shared.NewPool("restart", maxWorkers, len(restartContainers)) + for _, c := range restartContainers { + ctr := c + timeout := ctr.StopTimeout() + if useTimeout { + timeout = inputTimeout + } + pool.Add(shared.Job{ + ID: ctr.ID(), + Fn: func() error { + err := ctr.RestartWithTimeout(ctx, timeout) + if err != nil { + logrus.Debugf("Failed to restart container %s: %s", ctr.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} + +// Top display the running processes of a container +func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) { + var ( + descriptors []string + container *libpod.Container + err error + ) + if cli.Latest { + descriptors = cli.InputArgs + container, err = r.Runtime.GetLatestContainer() + } else { + descriptors = cli.InputArgs[1:] + container, err = r.Runtime.LookupContainer(cli.InputArgs[0]) + } + if err != nil { + return nil, errors.Wrapf(err, "unable to lookup requested container") + } + return container.Top(descriptors) +} + +// Prune removes stopped containers +func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + err error + ) + + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + filter := func(c *libpod.Container) bool { + state, err := c.State() + if err != nil { + logrus.Error(err) + return false + } + if c.PodID() != "" { + return false + } + if state == libpod.ContainerStateStopped || state == libpod.ContainerStateExited { + return true + } + return false + } + delContainers, err := r.Runtime.GetContainers(filter) + if err != nil { + return ok, failures, err + } + if len(delContainers) < 1 { + return ok, failures, err + } + pool := shared.NewPool("prune", maxWorkers, len(delContainers)) + for _, c := range delContainers { + ctr := c + pool.Add(shared.Job{ + ID: ctr.ID(), + Fn: func() error { + err := r.Runtime.RemoveContainer(ctx, ctr, force, false) + if err != nil { + logrus.Debugf("Failed to prune container %s: %s", ctr.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} + +// CleanupContainers any leftovers bits of stopped containers +func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, ctr := range ctrs { + if cli.Remove { + err = removeContainer(ctx, ctr, r) + } else { + err = cleanupContainer(ctx, ctr, r) + } + + if err == nil { + ok = append(ok, ctr.ID()) + } else { + failures[ctr.ID()] = err + } + } + return ok, failures, nil +} + +func removeContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error { + if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil { + return errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) + } + return nil +} + +func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error { + if err := ctr.Cleanup(ctx); err != nil { + return errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) + } + return nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index cb61871bf..a3a48a564 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -45,6 +45,12 @@ func (c *Container) ID() string { return c.config.ID } +// Restart a single container +func (c *Container) Restart(timeout int64) error { + _, err := iopodman.RestartContainer().Call(c.Runtime.Conn, c.ID(), timeout) + return err +} + // Pause a container func (c *Container) Pause() error { _, err := iopodman.PauseContainer().Call(c.Runtime.Conn, c.ID()) @@ -132,6 +138,23 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { }, nil } +// GetAllContainers returns all containers in a slice +func (r *LocalRuntime) GetAllContainers() ([]*Container, error) { + var containers []*Container + ctrs, err := iopodman.GetContainersByContext().Call(r.Conn, true, false, []string{}) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + container, err := r.LookupContainer(ctr) + if err != nil { + return nil, err + } + containers = append(containers, container) + } + return containers, nil +} + func (r *LocalRuntime) LookupContainersWithStatus(filters []string) ([]*Container, error) { var containers []*Container ctrs, err := iopodman.GetContainersByStatus().Call(r.Conn, filters) @@ -561,7 +584,10 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er } inputStream := os.Stdin if c.NoStdin { - inputStream = nil + inputStream, err = os.Open(os.DevNull) + if err != nil { + return err + } } errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys) if err != nil { @@ -753,3 +779,112 @@ func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.Unp } return ok, failures, nil } + +// Restart restarts a container over varlink +func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) ([]string, map[string]error, error) { + var ( + containers []*Container + restartContainers []*Container + err error + ok = []string{} + failures = map[string]error{} + ) + useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed + inputTimeout := c.Timeout + + if c.Latest { + lastCtr, err := r.GetLatestContainer() + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get latest container") + } + restartContainers = append(restartContainers, lastCtr) + } else if c.Running { + containers, err = r.LookupContainersWithStatus([]string{libpod.ContainerStateRunning.String()}) + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, containers...) + } else if c.All { + containers, err = r.GetAllContainers() + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, containers...) + } else { + for _, id := range c.InputArgs { + ctr, err := r.LookupContainer(id) + if err != nil { + return nil, nil, err + } + restartContainers = append(restartContainers, ctr) + } + } + + for _, c := range restartContainers { + c := c + timeout := c.config.StopTimeout + if useTimeout { + timeout = inputTimeout + } + err := c.Restart(int64(timeout)) + if err != nil { + failures[c.ID()] = err + } else { + ok = append(ok, c.ID()) + } + } + return ok, failures, nil +} + +// Top display the running processes of a container +func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) { + var ( + ctr *Container + err error + descriptors []string + ) + if cli.Latest { + ctr, err = r.GetLatestContainer() + descriptors = cli.InputArgs + } else { + ctr, err = r.LookupContainer(cli.InputArgs[0]) + descriptors = cli.InputArgs[1:] + } + if err != nil { + return nil, err + } + return iopodman.Top().Call(r.Conn, ctr.ID(), descriptors) +} + +// Prune removes stopped containers +func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) { + + var ( + ok = []string{} + failures = map[string]error{} + ctrs []*Container + err error + ) + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + filters := []string{libpod.ContainerStateExited.String()} + ctrs, err = r.LookupContainersWithStatus(filters) + if err != nil { + return ok, failures, err + } + for _, c := range ctrs { + c := c + _, err := iopodman.RemoveContainer().Call(r.Conn, c.ID(), false, false) + if err != nil { + failures[c.ID()] = err + } else { + ok = append(ok, c.ID()) + } + } + return ok, failures, nil +} + +// Cleanup any leftovers bits of stopped containers +func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) { + return nil, nil, errors.New("container cleanup not supported for remote clients") +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 753f7c944..0d840d65b 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -5,6 +5,7 @@ package adapter import ( "bufio" "context" + "github.com/containers/libpod/cmd/podman/shared" "io" "io/ioutil" "os" @@ -17,7 +18,6 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" @@ -57,8 +57,8 @@ type Volume struct { type VolumeFilter func(*Volume) bool // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it -func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { - runtime, err := libpodruntime.GetRuntime(c) +func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) { + runtime, err := libpodruntime.GetRuntime(ctx, c) if err != nil { return nil, err } @@ -119,8 +119,8 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for } // PruneImages is wrapper into PruneImages within the image pkg -func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { - return r.ImageRuntime().PruneImages(all) +func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) { + return r.ImageRuntime().PruneImages(ctx, all) } // Export is a wrapper to container export to a tarfile @@ -322,10 +322,6 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { fromStart bool eventsError error ) - options, err := shared.GenerateEventOptions(c.Filter, c.Since, c.Until) - if err != nil { - return errors.Wrapf(err, "unable to generate event options") - } tmpl, err := template.New("events").Parse(c.Format) if err != nil { return err @@ -335,7 +331,8 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { } eventChannel := make(chan *events.Event) go func() { - eventsError = r.Runtime.Events(fromStart, c.Stream, options, eventChannel) + readOpts := events.ReadOptions{FromStart: fromStart, Stream: c.Stream, Filters: c.Filter, EventChannel: eventChannel, Since: c.Since, Until: c.Until} + eventsError = r.Runtime.Events(readOpts) }() if eventsError != nil { diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index dcb0924ce..6102daccf 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -46,7 +46,7 @@ type LocalRuntime struct { } // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it -func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { +func GetRuntime(ctx context.Context, c *cliconfig.PodmanCommand) (*LocalRuntime, error) { runtime := RemoteRuntime{} conn, err := runtime.Connect() if err != nil { @@ -256,7 +256,7 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf // IsParent goes through the layers in the store and checks if i.TopLayer is // the parent of any other layer in store. Double check that image with that // layer exists as well. -func (ci *ContainerImage) IsParent() (bool, error) { +func (ci *ContainerImage) IsParent(context.Context) (bool, error) { return ci.remoteImage.isParent, nil } @@ -338,7 +338,7 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) } // PruneImages is the wrapper call for a remote-client to prune images -func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { +func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) { return iopodman.ImagesPrune().Call(r.Conn, all) } diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 270e431ad..6978370ef 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -38,7 +38,8 @@ type HostConfig struct { PidMode string `json:"PidMode"` Privileged bool `json:"Privileged"` PublishAllPorts bool `json:"PublishAllPorts"` //TODO - ReadonlyRootfs bool `json:"ReadonlyRootfs"` + ReadOnlyRootfs bool `json:"ReadonlyRootfs"` + ReadOnlyTmpfs bool `json:"ReadonlyTmpfs"` SecurityOpt []string `json:"SecurityOpt"` UTSMode string `json:"UTSMode"` UsernsMode string `json:"UsernsMode"` diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index e71d9d3db..064dedd45 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -113,6 +113,7 @@ type CreateConfig struct { PublishAll bool //publish-all Quiet bool //quiet ReadOnlyRootfs bool //read-only + ReadOnlyTmpfs bool //read-only-tmpfs Resources CreateResourceConfig Rm bool //rm StopSignal syscall.Signal // stop-signal diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 0371b6d4d..4cbed0ea4 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -341,6 +341,31 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } } + if config.ReadOnlyRootfs && config.ReadOnlyTmpfs { + options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} + for _, i := range []string{"/tmp", "/var/tmp"} { + if libpod.MountExists(g.Config.Mounts, i) { + continue + } + // Default options if nothing passed + tmpfsMnt := spec.Mount{ + Destination: i, + Type: "tmpfs", + Source: "tmpfs", + Options: options, + } + g.AddMount(tmpfsMnt) + } + if !libpod.MountExists(g.Config.Mounts, "/run") { + tmpfsMnt := spec.Mount{ + Destination: "/run", + Type: "tmpfs", + Source: "tmpfs", + Options: append(options, "noexec", "size=65536k"), + } + g.AddMount(tmpfsMnt) + } + } for name, val := range config.Env { g.AddProcessEnv(name, val) } diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 9e2a265be..6c62d3514 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -53,7 +53,13 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st if err != nil { return call.ReplyErrorOccurred(err.Error()) } - + state, err := ctr.State() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if !start && state != libpod.ContainerStateRunning { + return call.ReplyErrorOccurred("container must be running to attach") + } reader, writer, _, pw, streams := setupStreams(call) go func() { @@ -62,10 +68,10 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st } }() - if start { - finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan) - } else { + if state == libpod.ContainerStateRunning { finalErr = attach(ctr, streams, detachKeys, resize, errChan) + } else { + finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan) } if finalErr != libpod.ErrDetach && finalErr != nil { diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 237407050..872c7bc26 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -733,3 +733,16 @@ func newPodmanLogLine(line *libpod.LogLine) iopodman.LogLine { Cid: line.CID, } } + +// Top displays information about a container's running processes +func (i *LibpodAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors []string) error { + ctr, err := i.Runtime.LookupContainer(nameOrID) + if err != nil { + return call.ReplyContainerNotFound(ctr.ID(), err.Error()) + } + topInfo, err := ctr.Top(descriptors) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyTop(topInfo) +} diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go index 1e5696fbe..f9a9d9321 100644 --- a/pkg/varlinkapi/events.go +++ b/pkg/varlinkapi/events.go @@ -6,7 +6,6 @@ import ( "fmt" "time" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/events" ) @@ -23,19 +22,16 @@ func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since stream = true call.Continues = true } - filters, err := shared.GenerateEventOptions(filter, since, until) - if err != nil { - return call.ReplyErrorOccurred(err.Error()) - } if len(since) > 0 || len(until) > 0 { fromStart = true } eventChannel := make(chan *events.Event) go func() { - eventsError = i.Runtime.Events(fromStart, stream, filters, eventChannel) + readOpts := events.ReadOptions{FromStart: fromStart, Stream: stream, Filters: filter, EventChannel: eventChannel} + eventsError = i.Runtime.Events(readOpts) }() if eventsError != nil { - return call.ReplyErrorOccurred(err.Error()) + return call.ReplyErrorOccurred(eventsError.Error()) } for { event = <-eventChannel diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 470eadaeb..cecddf6b3 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -4,6 +4,7 @@ package varlinkapi import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -49,7 +50,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { } size, _ := image.Size(getContext()) - isParent, err := image.IsParent() + isParent, err := image.IsParent(context.TODO()) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -503,7 +504,7 @@ func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { return call.ReplyErrorOccurred(err.Error()) } if len(containers) == 0 { - if err := img.Remove(false); err != nil { + if err := img.Remove(context.TODO(), false); err != nil { return call.ReplyErrorOccurred(err.Error()) } deletedImages = append(deletedImages, img.ID()) @@ -739,7 +740,7 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman. // ImagesPrune .... func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error { - prunedImages, err := i.Runtime.ImageRuntime().PruneImages(all) + prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index a30a9b20b..a6fc211f6 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -407,9 +407,13 @@ func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegrat func (p *PodmanTestIntegration) Cleanup() { // Remove all containers stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) - // stopall.WaitWithDefaultTimeout() stopall.Wait(90) + podstop := p.Podman([]string{"pod", "stop", "-a", "-t", "0"}) + podstop.WaitWithDefaultTimeout() + podrm := p.Podman([]string{"pod", "rm", "-fa"}) + podrm.WaitWithDefaultTimeout() + session := p.Podman([]string{"rm", "-fa"}) session.Wait(90) diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index 321d93757..5ac5c9860 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -39,6 +39,7 @@ var _ = Describe("Podman events", func() { // Perhaps a future version of this test would put events in a go func and send output back over a channel // while events occur. It("podman events", func() { + Skip("need to verify images have correct packages for journald") _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false"}) @@ -47,17 +48,17 @@ var _ = Describe("Podman events", func() { }) It("podman events with an event filter", func() { - SkipIfRemote() + Skip("need to verify images have correct packages for journald") _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - Expect(len(result.OutputToStringArray())).To(Equal(1)) + Expect(len(result.OutputToStringArray()) >= 1) }) It("podman events with an event filter and container=cid", func() { - SkipIfRemote() + Skip("need to verify images have correct packages for journald") _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) _, ec2, cid2 := podmanTest.RunLsContainer("") @@ -69,32 +70,33 @@ var _ = Describe("Podman events", func() { Expect(!strings.Contains(result.OutputToString(), cid2)) }) - It("podman events with a type", func() { - SkipIfRemote() - _, ec, _ := podmanTest.RunLsContainer("") + It("podman events with a type and filter container=id", func() { + Skip("need to verify images have correct packages for journald") + _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) - result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"}) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod", "--filter", fmt.Sprintf("container=%s", cid)}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(len(result.OutputToStringArray())).To(Equal(0)) }) It("podman events with a type", func() { - SkipIfRemote() - setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobar", ALPINE, "top"}) + Skip("need to verify images have correct packages for journald") + setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobarpod", ALPINE, "top"}) setup.WaitWithDefaultTimeout() - stop := podmanTest.Podman([]string{"pod", "stop", "foobar"}) + stop := podmanTest.Podman([]string{"pod", "stop", "foobarpod"}) stop.WaitWithDefaultTimeout() Expect(stop.ExitCode()).To(Equal(0)) Expect(setup.ExitCode()).To(Equal(0)) - result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"}) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod", "--filter", "pod=foobarpod"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) fmt.Println(result.OutputToStringArray()) - Expect(len(result.OutputToStringArray())).To(Equal(2)) + Expect(len(result.OutputToStringArray()) >= 2) }) It("podman events --since", func() { + Skip("need to verify images have correct packages for journald") _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1m"}) @@ -103,6 +105,7 @@ var _ = Describe("Podman events", func() { }) It("podman events --until", func() { + Skip("need to verify images have correct packages for journald") _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) test := podmanTest.Podman([]string{"events", "--help"}) diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 867844c32..10ca9ac47 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -13,7 +13,6 @@ import ( ) func SkipIfRemote() { - ginkgo.Skip("This function is not enabled for remote podman") } func SkipIfRootless() { diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 544d54b50..377c9f5e1 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -39,7 +39,6 @@ var _ = Describe("Podman prune", func() { }) It("podman container prune containers", func() { - SkipIfRemote() top := podmanTest.RunTopContainer("") top.WaitWithDefaultTimeout() Expect(top.ExitCode()).To(Equal(0)) @@ -102,8 +101,6 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune pods", func() { - SkipIfRemote() - session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 1daf63a0e..7a9a466d8 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/test/system/005-info.bats b/test/system/005-info.bats index c64b011bd..47c7a52fc 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -3,7 +3,7 @@ load helpers @test "podman info - basic test" { - skip_if_remote + skip_if_remote "capitalization inconsistencies" run_podman info @@ -28,7 +28,7 @@ RunRoot: } @test "podman info - json" { - skip_if_remote + skip_if_remote "capitalization inconsistencies" run_podman info --format=json diff --git a/test/system/030-run.bats b/test/system/030-run.bats index bdbe724ef..a29b1adc3 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -3,8 +3,6 @@ load helpers @test "podman run - basic tests" { - skip_if_remote - rand=$(random_string 30) tests=" true | 0 | diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index 5736e0939..055865c8d 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -6,8 +6,6 @@ load helpers @test "podman logs - basic test" { - skip_if_remote - rand_string=$(random_string 40) run_podman create $IMAGE echo $rand_string diff --git a/test/system/070-build.bats b/test/system/070-build.bats index c6a25093f..53acf6edd 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -6,7 +6,11 @@ load helpers @test "podman build - basic test" { - skip_if_remote + if [[ "$PODMAN" =~ -remote ]]; then + if [ "$(id -u)" -ne 0 ]; then + skip "unreliable with podman-remote and rootless; #2972" + fi + fi rand_filename=$(random_string 20) rand_content=$(random_string 50) diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index 0358b3beb..738d8d87b 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -31,6 +31,12 @@ die() { echo "#| FAIL: $*" >&2 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 + # Show permissions of directories from here on up + while expr "$path" : "/var/lib/containers" >/dev/null; do + echo "#| $(ls -ld $path)" + path=$(dirname $path) + done + exit 1 } @@ -65,8 +71,10 @@ EOF # get podman image and container storage directories run_podman info --format '{{.store.GraphRoot}}' + is "$output" "/var/lib/containers/storage" "GraphRoot in expected place" GRAPH_ROOT="$output" run_podman info --format '{{.store.RunRoot}}' + is "$output" "/var/run/containers/storage" "RunRoot in expected place" RUN_ROOT="$output" # The main test: find all world-writable files or directories underneath diff --git a/vendor.conf b/vendor.conf index 886e4794a..02283beb9 100644 --- a/vendor.conf +++ b/vendor.conf @@ -19,9 +19,10 @@ github.com/containers/image v1.5.1 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 -github.com/containers/storage v1.12.1 -github.com/containers/psgo v1.2 +github.com/containers/storage v1.12.5 +github.com/containers/psgo v1.2.1 github.com/coreos/go-systemd v14 +github.com/coreos/pkg v4 github.com/cri-o/ocicni 0c180f981b27ef6036fa5be29bcb4dd666e406eb github.com/cyphar/filepath-securejoin v0.2.1 github.com/davecgh/go-spew v1.1.0 @@ -93,7 +94,7 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199 -github.com/containers/buildah fcc12bdadf6a5fab77e62e1bd12663bb6fbc3eda +github.com/containers/buildah v1.8.0 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 github.com/fsouza/go-dockerclient v1.3.0 diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go index d42246d53..d67a481f1 100644 --- a/vendor/github.com/containers/buildah/add.go +++ b/vendor/github.com/containers/buildah/add.go @@ -292,7 +292,7 @@ func addHelper(excludes []DockerIgnore, extract bool, dest string, destfi os.Fil break } // combine the filename with the dest directory - fpath := strings.TrimPrefix(path, options.ContextDir) + fpath := strings.TrimPrefix(path, esrc) if err = copyFileWithTar(path, filepath.Join(dest, fpath)); err != nil { return errors.Wrapf(err, "error copying %q to %q", path, dest) } diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 8b076630f..b6e6545ec 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -26,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.8-dev" + Version = "1.8.0" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to @@ -282,6 +282,8 @@ type CommonBuildOptions struct { CPUSetCPUs string // CPUSetMems memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. CPUSetMems string + // HTTPProxy determines whether *_proxy env vars from the build host are passed into the container. + HTTPProxy bool // Memory is the upper limit (in bytes) on how much memory running containers can use. Memory int64 // DNSSearch is the list of DNS search domains to add to the build container's /etc/resolv.conf diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index ff39c2f24..1c3ac65f3 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -512,7 +512,7 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io logNamespaceDiagnostics(spec) // If we have configured ID mappings, set them here so that they can apply to the child. - hostUidmap, hostGidmap, err := util.GetHostIDMappings("") + hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("") if err != nil { return 1, err } diff --git a/vendor/github.com/containers/buildah/chroot/selinux.go b/vendor/github.com/containers/buildah/chroot/selinux.go index 3e62d743d..08e8f998b 100644 --- a/vendor/github.com/containers/buildah/chroot/selinux.go +++ b/vendor/github.com/containers/buildah/chroot/selinux.go @@ -13,7 +13,7 @@ import ( // setSelinuxLabel sets the process label for child processes that we'll start. func setSelinuxLabel(spec *specs.Spec) error { logrus.Debugf("setting selinux label") - if spec.Process.SelinuxLabel != "" && selinux.EnforceMode() != selinux.Disabled { + if spec.Process.SelinuxLabel != "" && selinux.GetEnabled() { if err := label.SetProcessLabel(spec.Process.SelinuxLabel); err != nil { return errors.Wrapf(err, "error setting process label to %q", spec.Process.SelinuxLabel) } diff --git a/vendor/github.com/containers/buildah/commit.go b/vendor/github.com/containers/buildah/commit.go index 5e73be881..05d1550b3 100644 --- a/vendor/github.com/containers/buildah/commit.go +++ b/vendor/github.com/containers/buildah/commit.go @@ -64,12 +64,9 @@ type CommitOptions struct { // manifest of the new image will reference the blobs rather than // on-disk layers. BlobDirectory string - - // OnBuild is a list of commands to be run by images based on this image - OnBuild []string - // Parent is the base image that this image was created by. - Parent string - + // EmptyLayer tells the builder to omit the diff for the working + // container. + EmptyLayer bool // OmitTimestamp forces epoch 0 as created timestamp to allow for // deterministic, content-addressable builds. OmitTimestamp bool @@ -169,7 +166,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options } } // Build an image reference from which we can copy the finished image. - src, err := b.makeImageRef(options.PreferredManifestType, options.Parent, exportBaseLayers, options.Squash, options.BlobDirectory, options.Compression, options.HistoryTimestamp, options.OmitTimestamp) + src, err := b.makeImageRef(options, exportBaseLayers) if err != nil { return imgID, nil, "", errors.Wrapf(err, "error computing layer digests and building metadata for container %q", b.ContainerID) } diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 1cd329c85..215920cc3 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -56,6 +56,7 @@ type containerImageRef struct { preferredManifestType string exporting bool squash bool + emptyLayer bool tarPath func(path string) (io.ReadCloser, error) parent string blobDirectory string @@ -184,7 +185,7 @@ func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, if err := json.Unmarshal(i.dconfig, &dimage); err != nil { return v1.Image{}, v1.Manifest{}, docker.V2Image{}, docker.V2S2Manifest{}, err } - dimage.Parent = docker.ID(digest.FromString(i.parent)) + dimage.Parent = docker.ID(i.parent) // Always replace this value, since we're newer than our base image. dimage.Created = created // Clear the list of diffIDs, since we always repopulate it. @@ -290,6 +291,11 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System if err != nil { return nil, errors.Wrapf(err, "unable to locate layer %q", layerID) } + // If we're up to the final layer, but we don't want to include + // a diff for it, we're done. + if i.emptyLayer && layerID == i.layerID { + continue + } // If we're not re-exporting the data, and we're reusing layers individually, reuse // the blobsum and diff IDs. if !i.exporting && !i.squash && layerID != i.layerID { @@ -433,7 +439,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System CreatedBy: i.createdBy, Author: oimage.Author, Comment: i.historyComment, - EmptyLayer: false, + EmptyLayer: i.emptyLayer, } oimage.History = append(oimage.History, onews) dnews := docker.V2S2History{ @@ -441,11 +447,11 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System CreatedBy: i.createdBy, Author: dimage.Author, Comment: i.historyComment, - EmptyLayer: false, + EmptyLayer: i.emptyLayer, } dimage.History = append(dimage.History, dnews) appendHistory(i.postEmptyLayers) - dimage.Parent = docker.ID(digest.FromString(i.parent)) + dimage.Parent = docker.ID(i.parent) // Sanity check that we didn't just create a mismatch between non-empty layers in the // history and the number of diffIDs. @@ -636,7 +642,7 @@ func (i *containerImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, return ioutils.NewReadCloserWrapper(layerFile, closer), size, nil } -func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squash bool, blobDirectory string, compress archive.Compression, historyTimestamp *time.Time, omitTimestamp bool) (types.ImageReference, error) { +func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.ImageReference, error) { var name reference.Named container, err := b.store.Container(b.ContainerID) if err != nil { @@ -647,6 +653,7 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa name = parsed } } + manifestType := options.PreferredManifestType if manifestType == "" { manifestType = OCIv1ImageManifest } @@ -659,8 +666,8 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa return nil, errors.Wrapf(err, "error encoding docker-format image configuration %#v", b.Docker) } created := time.Now().UTC() - if historyTimestamp != nil { - created = historyTimestamp.UTC() + if options.HistoryTimestamp != nil { + created = options.HistoryTimestamp.UTC() } createdBy := b.CreatedBy() if createdBy == "" { @@ -670,13 +677,21 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa } } - if omitTimestamp { + if options.OmitTimestamp { created = time.Unix(0, 0) } + parent := "" + if b.FromImageID != "" { + parentDigest := digest.NewDigestFromEncoded(digest.Canonical, b.FromImageID) + if parentDigest.Validate() == nil { + parent = parentDigest.String() + } + } + ref := &containerImageRef{ store: b.store, - compression: compress, + compression: options.Compression, name: name, names: container.Names, containerID: container.ID, @@ -690,10 +705,11 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa annotations: b.Annotations(), preferredManifestType: manifestType, exporting: exporting, - squash: squash, + squash: options.Squash, + emptyLayer: options.EmptyLayer, tarPath: b.tarPath(), parent: parent, - blobDirectory: blobDirectory, + blobDirectory: options.BlobDirectory, preEmptyLayers: b.PrependedEmptyLayers, postEmptyLayers: b.AppendedEmptyLayers, } diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index b692d3bcf..d9909cdc8 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -28,7 +28,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" docker "github.com/fsouza/go-dockerclient" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/openshift/imagebuilder" "github.com/openshift/imagebuilder/dockerfile/parser" @@ -215,9 +215,12 @@ type Executor struct { forceRmIntermediateCtrs bool imageMap map[string]string // Used to map images that we create to handle the AS construct. containerMap map[string]*buildah.Builder // Used to map from image names to only-created-for-the-rootfs containers. + baseMap map[string]bool // Holds the names of every base image, as given. + rootfsMap map[string]bool // Holds the names of every stage whose rootfs is referenced in a COPY or ADD instruction. blobDirectory string excludes []string unusedArgs map[string]struct{} + buildArgs map[string]string } // StageExecutor bundles up what we need to know when executing one stage of a @@ -480,6 +483,19 @@ func (s *StageExecutor) volumeCacheRestore() error { // imagebuilder tells us the instruction was "ADD" and not "COPY". func (s *StageExecutor) Copy(excludes []string, copies ...imagebuilder.Copy) error { for _, copy := range copies { + // If the file exists, check to see if it's a symlink. + // If it is a symlink, convert to it's target otherwise + // the symlink will be overwritten. + fileDest, _ := os.Lstat(filepath.Join(s.mountPoint, copy.Dest)) + if fileDest != nil { + if fileDest.Mode()&os.ModeSymlink != 0 { + if symLink, err := resolveSymlink(s.mountPoint, copy.Dest); err == nil { + copy.Dest = symLink + } else { + return errors.Wrapf(err, "error reading symbolic link to %q", copy.Dest) + } + } + } if copy.Download { logrus.Debugf("ADD %#v, %#v", excludes, copy) } else { @@ -590,7 +606,7 @@ func (s *StageExecutor) Run(run imagebuilder.Run, config docker.Config) error { // UnrecognizedInstruction is called when we encounter an instruction that the // imagebuilder parser didn't understand. func (s *StageExecutor) UnrecognizedInstruction(step *imagebuilder.Step) error { - errStr := fmt.Sprintf("Build error: Unknown instruction: %q ", step.Command) + errStr := fmt.Sprintf("Build error: Unknown instruction: %q ", strings.ToUpper(step.Command)) err := fmt.Sprintf(errStr+"%#v", step) if s.executor.ignoreUnrecognizedInstructions { logrus.Debugf(err) @@ -610,7 +626,7 @@ func (s *StageExecutor) UnrecognizedInstruction(step *imagebuilder.Step) error { } // NewExecutor creates a new instance of the imagebuilder.Executor interface. -func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { +func NewExecutor(store storage.Store, options BuildOptions, mainNode *parser.Node) (*Executor, error) { excludes, err := imagebuilder.ParseDockerignore(options.ContextDirectory) if err != nil { return nil, err @@ -656,8 +672,11 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, imageMap: make(map[string]string), containerMap: make(map[string]*buildah.Builder), + baseMap: make(map[string]bool), + rootfsMap: make(map[string]bool), blobDirectory: options.BlobDirectory, unusedArgs: make(map[string]struct{}), + buildArgs: options.Args, } if exec.err == nil { exec.err = os.Stderr @@ -679,6 +698,25 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { exec.unusedArgs[arg] = struct{}{} } } + for _, line := range mainNode.Children { + node := line + for node != nil { // tokens on this line, though we only care about the first + switch strings.ToUpper(node.Value) { // first token - instruction + case "ARG": + arg := node.Next + if arg != nil { + // We have to be careful here - it's either an argument + // and value, or just an argument, since they can be + // separated by either "=" or whitespace. + list := strings.SplitN(arg.Value, "=", 2) + if _, stillUnused := exec.unusedArgs[list[0]]; stillUnused { + delete(exec.unusedArgs, list[0]) + } + } + } + break + } + } return &exec, nil } @@ -845,9 +883,9 @@ func (b *Executor) resolveNameToImageRef(output string) (types.ImageReference, e return imageRef, nil } -// stepRequiresCommit indicates whether or not the step should be followed by -// committing the in-progress container to create an intermediate image. -func (*StageExecutor) stepRequiresCommit(step *imagebuilder.Step) bool { +// stepRequiresLayer indicates whether or not the step should be followed by +// committing a layer container when creating an intermediate image. +func (*StageExecutor) stepRequiresLayer(step *imagebuilder.Step) bool { switch strings.ToUpper(step.Command) { case "ADD", "COPY", "RUN": return true @@ -875,6 +913,10 @@ func (s *StageExecutor) getImageRootfs(ctx context.Context, stage imagebuilder.S func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, base string) (imgID string, ref reference.Canonical, err error) { ib := stage.Builder checkForLayers := s.executor.layers && s.executor.useCache + moreStages := s.index < s.stages-1 + lastStage := !moreStages + imageIsUsedLater := moreStages && (s.executor.baseMap[stage.Name] || s.executor.baseMap[fmt.Sprintf("%d", stage.Position)]) + rootfsIsUsedLater := moreStages && (s.executor.rootfsMap[stage.Name] || s.executor.rootfsMap[fmt.Sprintf("%d", stage.Position)]) // If the base image's name corresponds to the result of an earlier // stage, substitute that image's ID for the base image's name here. @@ -896,7 +938,8 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // A helper function to only log "COMMIT" as an explicit step if it's // the very last step of a (possibly multi-stage) build. logCommit := func(output string, instruction int) { - if instruction < len(children)-1 || s.index < s.stages-1 { + moreInstructions := instruction < len(children)-1 + if moreInstructions || moreStages { return } commitMessage := "COMMIT" @@ -921,7 +964,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // squash the contents of the base image. Whichever is // the case, we need to commit() to create a new image. logCommit(s.output, -1) - if imgID, ref, err = s.commit(ctx, ib, getCreatedBy(nil), s.output); err != nil { + if imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(nil), false, s.output); err != nil { return "", nil, errors.Wrapf(err, "error committing base container") } } else { @@ -936,6 +979,8 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b } for i, node := range children { + moreInstructions := i < len(children)-1 + lastInstruction := !moreInstructions // Resolve any arguments in this instruction. step := ib.Step() if err := step.Resolve(node); err != nil { @@ -946,30 +991,19 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b s.executor.log("%s", step.Original) } - // If this instruction declares an argument, remove it from the - // set of arguments that we were passed but which we haven't - // yet seen used by the Dockerfile. - if step.Command == "arg" { - for _, Arg := range step.Args { - list := strings.SplitN(Arg, "=", 2) - if _, stillUnused := s.executor.unusedArgs[list[0]]; stillUnused { - delete(s.executor.unusedArgs, list[0]) - } - } - } - // Check if there's a --from if the step command is COPY or // ADD. Set copyFrom to point to either the context directory // or the root of the container from the specified stage. s.copyFrom = s.executor.contextDir for _, n := range step.Flags { - if strings.Contains(n, "--from") && (step.Command == "copy" || step.Command == "add") { + command := strings.ToUpper(step.Command) + if strings.Contains(n, "--from") && (command == "COPY" || command == "ADD") { var mountPoint string arr := strings.Split(n, "=") otherStage, ok := s.executor.stages[arr[1]] if !ok { if mountPoint, err = s.getImageRootfs(ctx, stage, arr[1]); err != nil { - return "", nil, errors.Errorf("%s --from=%s: no stage or image found with that name", step.Command, arr[1]) + return "", nil, errors.Errorf("%s --from=%s: no stage or image found with that name", command, arr[1]) } } else { mountPoint = otherStage.mountPoint @@ -984,7 +1018,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // contents of any volumes declared between now and when we // finish. noRunsRemaining := false - if i < len(children)-1 { + if moreInstructions { noRunsRemaining = !ib.RequiresStart(&parser.Node{Children: children[i+1:]}) } @@ -996,24 +1030,29 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b logrus.Debugf("%v", errors.Wrapf(err, "error building at step %+v", *step)) return "", nil, errors.Wrapf(err, "error building at STEP \"%s\"", step.Message) } - if i < len(children)-1 { + if moreInstructions { // There are still more instructions to process // for this stage. Make a note of the // instruction in the history that we'll write // for the image when we eventually commit it. now := time.Now() - s.builder.AddPrependedEmptyLayer(&now, getCreatedBy(node), "", "") + s.builder.AddPrependedEmptyLayer(&now, s.executor.getCreatedBy(node), "", "") continue } else { // This is the last instruction for this stage, // so we should commit this container to create - // an image. - logCommit(s.output, i) - imgID, ref, err = s.commit(ctx, ib, getCreatedBy(node), s.output) - if err != nil { - return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step) + // an image, but only if it's the last one, or + // if it's used as the basis for a later stage. + if lastStage || imageIsUsedLater { + logCommit(s.output, i) + imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), false, s.output) + if err != nil { + return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step) + } + logImageID(imgID) + } else { + imgID = "" } - logImageID(imgID) break } } @@ -1028,18 +1067,14 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // If we have to commit for this instruction, only assign the // stage's configured output name to the last layer. - if i == len(children)-1 { + if lastInstruction { commitName = s.output } // If we're using the cache, and we've managed to stick with // cached images so far, look for one that matches what we // expect to produce for this instruction. - // Only check at steps where we commit, so that we don't - // abandon the cache at this step just because we can't find an - // image with a history entry in it that we wouldn't have - // committed. - if checkForLayers && (s.stepRequiresCommit(step) || i == len(children)-1) && !(s.executor.squash && i == len(children)-1 && s.index == s.stages-1) { + if checkForLayers && !(s.executor.squash && lastInstruction && lastStage) { cacheID, err = s.layerExists(ctx, node, children[:i]) if err != nil { return "", nil, errors.Wrap(err, "error checking if cached image exists from a previous build") @@ -1059,17 +1094,32 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // the last step in this stage, add the name to the // image. imgID = cacheID - if commitName != "" && (s.stepRequiresCommit(step) || i == len(children)-1) { - logCommit(s.output, i) + if commitName != "" { + logCommit(commitName, i) if imgID, ref, err = s.copyExistingImage(ctx, cacheID, commitName); err != nil { return "", nil, err } logImageID(imgID) } // Update our working container to be based off of the - // cached image, in case we need to read content from - // its root filesystem. - rebase = true + // cached image, if we might need to use it as a basis + // for the next instruction, or if we need the root + // filesystem to match the image contents for the sake + // of a later stage that wants to copy content from it. + rebase = moreInstructions || rootfsIsUsedLater + // If the instruction would affect our configuration, + // process the configuration change so that, if we fall + // off the cache path, the filesystem changes from the + // last cache image will be all that we need, since we + // still don't want to restart using the image's + // configuration blob. + if !s.stepRequiresLayer(step) { + err := ib.Run(step, s, noRunsRemaining) + if err != nil { + logrus.Debugf("%v", errors.Wrapf(err, "error building at step %+v", *step)) + return "", nil, errors.Wrapf(err, "error building at STEP \"%s\"", step.Message) + } + } } else { // If we didn't find a cached image that we could just reuse, // process the instruction directly. @@ -1078,36 +1128,20 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b logrus.Debugf("%v", errors.Wrapf(err, "error building at step %+v", *step)) return "", nil, errors.Wrapf(err, "error building at STEP \"%s\"", step.Message) } - if s.stepRequiresCommit(step) || i == len(children)-1 { - // Either this is the last instruction, or - // there are more instructions and we need to - // create a layer from this one before - // continuing. - // TODO: only commit for the last instruction - // case if we need to use this stage's image as - // a base image later, or if we're the final - // stage. - logCommit(s.output, i) - imgID, ref, err = s.commit(ctx, ib, getCreatedBy(node), commitName) - if err != nil { - return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step) - } - logImageID(imgID) - // We only need to build a new container rootfs - // using this image if we plan on making - // further changes to it. Subsequent stages - // that just want to use the rootfs as a source - // for COPY or ADD will be content with what we - // already have. - rebase = i < len(children)-1 - } else { - // There are still more instructions to process - // for this stage, and we don't need to commit - // here. Make a note of the instruction in the - // history for the next commit. - now := time.Now() - s.builder.AddPrependedEmptyLayer(&now, getCreatedBy(node), "", "") + // Create a new image, maybe with a new layer. + logCommit(s.output, i) + imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), !s.stepRequiresLayer(step), commitName) + if err != nil { + return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step) } + logImageID(imgID) + // We only need to build a new container rootfs + // using this image if we plan on making + // further changes to it. Subsequent stages + // that just want to use the rootfs as a source + // for COPY or ADD will be content with what we + // already have. + rebase = moreInstructions } if rebase { @@ -1122,8 +1156,6 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b // creating a new working container with the // just-committed or updated cached image as its new // base image. - // TODO: only create a new container if we know that - // we'll need the updated root filesystem. if _, err := s.prepare(ctx, stage, imgID, false, true); err != nil { return "", nil, errors.Wrap(err, "error preparing container for next step") } @@ -1195,13 +1227,13 @@ func (s *StageExecutor) layerExists(ctx context.Context, currNode *parser.Node, // it means that this image is potentially a cached intermediate image from a previous // build. Next we double check that the history of this image is equivalent to the previous // lines in the Dockerfile up till the point we are at in the build. - if layer.Parent == s.executor.topLayers[len(s.executor.topLayers)-1] { + if layer.Parent == s.executor.topLayers[len(s.executor.topLayers)-1] || layer.ID == s.executor.topLayers[len(s.executor.topLayers)-1] { history, err := s.executor.getImageHistory(ctx, image.ID) if err != nil { return "", errors.Wrapf(err, "error getting history of %q", image.ID) } // children + currNode is the point of the Dockerfile we are currently at. - if historyMatches(append(children, currNode), history) { + if s.executor.historyMatches(append(children, currNode), history) { // This checks if the files copied during build have been changed if the node is // a COPY or ADD command. filesMatch, err := s.copiedFilesMatch(currNode, history[len(history)-1].Created) @@ -1225,21 +1257,26 @@ func (b *Executor) getImageHistory(ctx context.Context, imageID string) ([]v1.Hi } ref, err := imageRef.NewImage(ctx, nil) if err != nil { - return nil, errors.Wrap(err, "error creating new image from reference") + return nil, errors.Wrapf(err, "error creating new image from reference to image %q", imageID) } + defer ref.Close() oci, err := ref.OCIConfig(ctx) if err != nil { - return nil, errors.Wrapf(err, "error getting oci config of image %q", imageID) + return nil, errors.Wrapf(err, "error getting possibly-converted OCI config of image %q", imageID) } return oci.History, nil } // getCreatedBy returns the command the image at node will be created by. -func getCreatedBy(node *parser.Node) string { +func (b *Executor) getCreatedBy(node *parser.Node) string { if node == nil { return "/bin/sh" } if node.Value == "run" { + buildArgs := b.getBuildArgs() + if buildArgs != "" { + return "|" + strconv.Itoa(len(strings.Split(buildArgs, " "))) + " " + buildArgs + " /bin/sh -c " + node.Original[4:] + } return "/bin/sh -c " + node.Original[4:] } return "/bin/sh -c #(nop) " + node.Original @@ -1249,12 +1286,23 @@ func getCreatedBy(node *parser.Node) string { // in the Dockerfile till the point of build we are at. // Used to verify whether a cache of the intermediate image exists and whether // to run the build again. -func historyMatches(children []*parser.Node, history []v1.History) bool { +func (b *Executor) historyMatches(children []*parser.Node, history []v1.History) bool { i := len(history) - 1 for j := len(children) - 1; j >= 0; j-- { instruction := children[j].Original if children[j].Value == "run" { instruction = instruction[4:] + buildArgs := b.getBuildArgs() + // If a previous image was built with some build-args but the new build process doesn't have any build-args + // specified, so compare the lengths of the old instruction with the current one + // 11 is the length of "/bin/sh -c " that is used to run the run commands + if buildArgs == "" && len(history[i].CreatedBy) > len(instruction)+11 { + return false + } + // There are build-args, so check if anything with the build-args has changed + if buildArgs != "" && !strings.Contains(history[i].CreatedBy, buildArgs) { + return false + } } if !strings.Contains(history[i].CreatedBy, instruction) { return false @@ -1264,6 +1312,18 @@ func historyMatches(children []*parser.Node, history []v1.History) bool { return true } +// getBuildArgs returns a string of the build-args specified during the build process +// it excludes any build-args that were not used in the build process +func (b *Executor) getBuildArgs() string { + var buildArgs []string + for k, v := range b.buildArgs { + if _, ok := b.unusedArgs[k]; !ok { + buildArgs = append(buildArgs, k+"="+v) + } + } + return strings.Join(buildArgs, " ") +} + // getFilesToCopy goes through node to get all the src files that are copied, added or downloaded. // It is possible for the Dockerfile to have src as hom*, which means all files that have hom as a prefix. // Another format is hom?.txt, which means all files that have that name format with the ? replaced by another character. @@ -1348,7 +1408,7 @@ func urlContentModified(url string, historyTime *time.Time) (bool, error) { // commit writes the container's contents to an image, using a passed-in tag as // the name if there is one, generating a unique ID-based one otherwise. -func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, createdBy, output string) (string, reference.Canonical, error) { +func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, createdBy string, emptyLayer bool, output string) (string, reference.Canonical, error) { var imageRef types.ImageReference if output != "" { imageRef2, err := s.executor.resolveNameToImageRef(output) @@ -1438,8 +1498,8 @@ func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, cr PreferredManifestType: s.executor.outputFormat, SystemContext: s.executor.systemContext, Squash: s.executor.squash, + EmptyLayer: emptyLayer, BlobDirectory: s.executor.blobDirectory, - Parent: s.builder.FromImageID, } imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options) if err != nil { @@ -1510,6 +1570,46 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image } defer cleanup() + // Build maps of every named base image and every referenced stage root + // filesystem. Individual stages can use them to determine whether or + // not they can skip certain steps near the end of their stages. + for _, stage := range stages { + node := stage.Node // first line + for node != nil { // each line + for _, child := range node.Children { // tokens on this line, though we only care about the first + switch strings.ToUpper(child.Value) { // first token - instruction + case "FROM": + if child.Next != nil { // second token on this line + base := child.Next.Value + if base != "scratch" { + // TODO: this didn't undergo variable and arg + // expansion, so if the AS clause in another + // FROM instruction uses argument values, + // we might not record the right value here. + b.baseMap[base] = true + logrus.Debugf("base: %q", base) + } + } + case "ADD", "COPY": + for _, flag := range child.Flags { // flags for this instruction + if strings.HasPrefix(flag, "--from=") { + // TODO: this didn't undergo variable and + // arg expansion, so if the previous stage + // was named using argument values, we might + // not record the right value here. + rootfs := flag[7:] + b.rootfsMap[rootfs] = true + logrus.Debugf("rootfs: %q", rootfs) + } + } + } + break + } + node = node.Next // next line + } + } + + // Run through the build stages, one at a time. for stageIndex, stage := range stages { var lastErr error @@ -1555,7 +1655,7 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image // If this is an intermediate stage, make a note of the ID, so // that we can look it up later. - if stageIndex < len(stages)-1 { + if stageIndex < len(stages)-1 && imageID != "" { b.imageMap[stage.Name] = imageID // We're not populating the cache with intermediate // images, so add this one to the list of images that @@ -1671,7 +1771,7 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options BuildOpt } mainNode.Children = append(mainNode.Children, additionalNode.Children...) } - exec, err := NewExecutor(store, options) + exec, err := NewExecutor(store, options, mainNode) if err != nil { return "", nil, errors.Wrapf(err, "error creating build executor") } diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go index 86bf7653b..0789c2b3c 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go +++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go @@ -129,20 +129,20 @@ func resolveModifiedTime(rootdir, filename, historyTime string) (bool, error) { func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) { var timeIsGreater bool - // the Walk below doesn't work if rootdir and path are equal - if rootdir == path { - return false, nil - } - // Convert historyTime from string to time.Time for comparison histTime, err := time.Parse(time.RFC3339Nano, historyTime) if err != nil { return false, errors.Wrapf(err, "error converting string to time.Time %q", historyTime) } + + // Since we are chroot in rootdir, we want a relative path, i.e (path - rootdir) + relPath, err := filepath.Rel(rootdir, path) + if err != nil { + return false, errors.Wrapf(err, "error making path %q relative to %q", path, rootdir) + } + // Walk the file tree and check the time stamps. - // Since we are chroot in rootdir, only want the path of the actual filename, i.e path - rootdir. - // +1 to account for the extra "/" (e.g rootdir=/home/user/mydir, path=/home/user/mydir/myfile.json) - err = filepath.Walk(path[len(rootdir)+1:], func(path string, info os.FileInfo, err error) error { + err = filepath.Walk(relPath, func(path string, info os.FileInfo, err error) error { // If using cached images, it is possible for files that are being copied to come from // previous build stages. But if using cached images, then the copied file won't exist // since a container won't have been created for the previous build stage and info will be nil. @@ -154,6 +154,9 @@ func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) { if info.Mode()&os.ModeSymlink == os.ModeSymlink { // Evaluate any symlink that occurs to get updated modified information resolvedPath, err := filepath.EvalSymlinks(path) + if err != nil && os.IsNotExist(err) { + return errors.Wrapf(errDanglingSymlink, "%q", path) + } if err != nil { return errors.Wrapf(err, "error evaluating symlink %q", path) } @@ -169,7 +172,12 @@ func modTimeIsGreater(rootdir, path string, historyTime string) (bool, error) { } return nil }) + if err != nil { + // if error is due to dangling symlink, ignore error and return nil + if errors.Cause(err) == errDanglingSymlink { + return false, nil + } return false, errors.Wrapf(err, "error walking file tree %q", path) } return timeIsGreater, err diff --git a/vendor/github.com/containers/buildah/imagebuildah/errors.go b/vendor/github.com/containers/buildah/imagebuildah/errors.go new file mode 100644 index 000000000..cf299656b --- /dev/null +++ b/vendor/github.com/containers/buildah/imagebuildah/errors.go @@ -0,0 +1,7 @@ +package imagebuildah + +import "errors" + +var ( + errDanglingSymlink = errors.New("error evaluating dangling symlink") +) diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index 6c4d14303..7fa0a7777 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -89,6 +89,7 @@ type FromAndBudResults struct { DNSSearch []string DNSServers []string DNSOptions []string + HttpProxy bool Isolation string Memory string MemorySwap string @@ -182,6 +183,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, fs.StringSliceVar(&flags.DNSSearch, "dns-search", []string{}, "Set custom DNS search domains") fs.StringSliceVar(&flags.DNSServers, "dns", []string{}, "Set custom DNS servers") fs.StringSliceVar(&flags.DNSOptions, "dns-option", []string{}, "Set custom DNS options") + fs.BoolVar(&flags.HttpProxy, "http-proxy", true, "pass thru HTTP Proxy environment variables") fs.StringVar(&flags.Isolation, "isolation", DefaultIsolation(), "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.") fs.StringVarP(&flags.Memory, "memory", "m", "", "memory limit (format: <number>[<unit>], where unit = b, k, m or g)") fs.StringVar(&flags.MemorySwap, "memory-swap", "", "swap limit equal to memory plus swap: '-1' to enable unlimited swap") diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index c4e3e4264..cc85136fd 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -86,6 +86,7 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { cpuPeriod, _ := c.Flags().GetUint64("cpu-period") cpuQuota, _ := c.Flags().GetInt64("cpu-quota") cpuShares, _ := c.Flags().GetUint64("cpu-shared") + httpProxy, _ := c.Flags().GetBool("http-proxy") ulimit, _ := c.Flags().GetStringSlice("ulimit") commonOpts := &buildah.CommonBuildOptions{ AddHost: addHost, @@ -98,6 +99,7 @@ func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { DNSSearch: dnsSearch, DNSServers: dnsServers, DNSOptions: dnsOptions, + HTTPProxy: httpProxy, Memory: memoryLimit, MemorySwap: memorySwap, ShmSize: c.Flag("shm-size").Value.String(), diff --git a/vendor/github.com/containers/buildah/pkg/unshare/unshare.go b/vendor/github.com/containers/buildah/pkg/unshare/unshare.go index 5b2e7d7d1..33232740e 100644 --- a/vendor/github.com/containers/buildah/pkg/unshare/unshare.go +++ b/vendor/github.com/containers/buildah/pkg/unshare/unshare.go @@ -3,6 +3,7 @@ package unshare import ( + "bufio" "bytes" "fmt" "io" @@ -15,7 +16,7 @@ import ( "sync" "syscall" - "github.com/containers/buildah/util" + "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/reexec" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -157,7 +158,7 @@ func (c *Cmd) Start() error { } if len(c.UidMappings) == 0 || len(c.GidMappings) == 0 { - uidmap, gidmap, err := util.GetHostIDMappings("") + uidmap, gidmap, err := GetHostIDMappings("") if err != nil { fmt.Fprintf(continueWrite, "error reading ID mappings in parent: %v", err) return errors.Wrapf(err, "error reading ID mappings in parent") @@ -352,7 +353,7 @@ func MaybeReexecUsingUserNamespace(evenForRoot bool) { // Read the set of ID mappings that we're allowed to use. Each // range in /etc/subuid and /etc/subgid file is a starting host // ID and a range size. - uidmap, gidmap, err = util.GetSubIDMappings(me.Username, me.Username) + uidmap, gidmap, err = GetSubIDMappings(me.Username, me.Username) bailOnError(err, "error reading allowed ID mappings") if len(uidmap) == 0 { logrus.Warnf("Found no UID ranges set aside for user %q in /etc/subuid.", me.Username) @@ -384,7 +385,7 @@ func MaybeReexecUsingUserNamespace(evenForRoot bool) { return } // Read the set of ID mappings that we're currently using. - uidmap, gidmap, err = util.GetHostIDMappings("") + uidmap, gidmap, err = GetHostIDMappings("") bailOnError(err, "error reading current ID mappings") // Just reuse them. for i := range uidmap { @@ -404,6 +405,16 @@ func MaybeReexecUsingUserNamespace(evenForRoot bool) { err = os.Setenv(UsernsEnvName, "1") bailOnError(err, "error setting %s=1 in environment", UsernsEnvName) + // Set the default isolation type to use the "rootless" method. + if _, present := os.LookupEnv("BUILDAH_ISOLATION"); !present { + if err = os.Setenv("BUILDAH_ISOLATION", "rootless"); err != nil { + if err := os.Setenv("BUILDAH_ISOLATION", "rootless"); err != nil { + logrus.Errorf("error setting BUILDAH_ISOLATION=rootless in environment: %v", err) + os.Exit(1) + } + } + } + // Reuse our stdio. cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout @@ -446,3 +457,89 @@ func ExecRunnable(cmd Runnable) { } os.Exit(0) } + +// getHostIDMappings reads mappings from the named node under /proc. +func getHostIDMappings(path string) ([]specs.LinuxIDMapping, error) { + var mappings []specs.LinuxIDMapping + f, err := os.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "error reading ID mappings from %q", path) + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) != 3 { + return nil, errors.Errorf("line %q from %q has %d fields, not 3", line, path, len(fields)) + } + cid, err := strconv.ParseUint(fields[0], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing container ID value %q from line %q in %q", fields[0], line, path) + } + hid, err := strconv.ParseUint(fields[1], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing host ID value %q from line %q in %q", fields[1], line, path) + } + size, err := strconv.ParseUint(fields[2], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "error parsing size value %q from line %q in %q", fields[2], line, path) + } + mappings = append(mappings, specs.LinuxIDMapping{ContainerID: uint32(cid), HostID: uint32(hid), Size: uint32(size)}) + } + return mappings, nil +} + +// GetHostIDMappings reads mappings for the specified process (or the current +// process if pid is "self" or an empty string) from the kernel. +func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { + if pid == "" { + pid = "self" + } + uidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/uid_map", pid)) + if err != nil { + return nil, nil, err + } + gidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/gid_map", pid)) + if err != nil { + return nil, nil, err + } + return uidmap, gidmap, nil +} + +// GetSubIDMappings reads mappings from /etc/subuid and /etc/subgid. +func GetSubIDMappings(user, group string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { + mappings, err := idtools.NewIDMappings(user, group) + if err != nil { + return nil, nil, errors.Wrapf(err, "error reading subuid mappings for user %q and subgid mappings for group %q", user, group) + } + var uidmap, gidmap []specs.LinuxIDMapping + for _, m := range mappings.UIDs() { + uidmap = append(uidmap, specs.LinuxIDMapping{ + ContainerID: uint32(m.ContainerID), + HostID: uint32(m.HostID), + Size: uint32(m.Size), + }) + } + for _, m := range mappings.GIDs() { + gidmap = append(gidmap, specs.LinuxIDMapping{ + ContainerID: uint32(m.ContainerID), + HostID: uint32(m.HostID), + Size: uint32(m.Size), + }) + } + return uidmap, gidmap, nil +} + +// ParseIDMappings parses mapping triples. +func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) { + uid, err := idtools.ParseIDMap(uidmap, "userns-uid-map") + if err != nil { + return nil, nil, err + } + gid, err := idtools.ParseIDMap(gidmap, "userns-gid-map") + if err != nil { + return nil, nil, err + } + return uid, gid, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/unshare/unshare_unsupported.go b/vendor/github.com/containers/buildah/pkg/unshare/unshare_unsupported.go index d8d5f6f7a..bf4d567b8 100644 --- a/vendor/github.com/containers/buildah/pkg/unshare/unshare_unsupported.go +++ b/vendor/github.com/containers/buildah/pkg/unshare/unshare_unsupported.go @@ -4,6 +4,9 @@ package unshare import ( "os" + + "github.com/containers/storage/pkg/idtools" + "github.com/opencontainers/runtime-spec/specs-go" ) const ( @@ -29,3 +32,14 @@ func RootlessEnv() []string { // MaybeReexecUsingUserNamespace re-exec the process in a new namespace func MaybeReexecUsingUserNamespace(evenForRoot bool) { } + +// GetHostIDMappings reads mappings for the specified process (or the current +// process if pid is "self" or an empty string) from the kernel. +func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { + return nil, nil, nil +} + +// ParseIDMappings parses mapping triples. +func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) { + return nil, nil, nil +} diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 5d28644d7..00eac8e39 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -865,7 +865,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i if err := g.AddOrReplaceLinuxNamespace(specs.UserNamespace, ""); err != nil { return false, nil, false, errors.Wrapf(err, "error adding new %q namespace for run", string(specs.UserNamespace)) } - hostUidmap, hostGidmap, err := util.GetHostIDMappings("") + hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("") if err != nil { return false, nil, false, err } @@ -983,6 +983,24 @@ func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, opti func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions) { g.ClearProcessEnv() + if b.CommonBuildOpts.HTTPProxy { + for _, envSpec := range []string{ + "http_proxy", + "HTTP_PROXY", + "https_proxy", + "HTTPS_PROXY", + "ftp_proxy", + "FTP_PROXY", + "no_proxy", + "NO_PROXY", + } { + envVal := os.Getenv(envSpec) + if envVal != "" { + g.AddProcessEnv(envSpec, envVal) + } + } + } + for _, envSpec := range append(b.Env(), options.Env...) { env := strings.SplitN(envSpec, "=", 2) if len(env) > 1 { diff --git a/vendor/github.com/containers/buildah/selinux.go b/vendor/github.com/containers/buildah/selinux.go index 2b850cf9f..e64eb6112 100644 --- a/vendor/github.com/containers/buildah/selinux.go +++ b/vendor/github.com/containers/buildah/selinux.go @@ -4,9 +4,12 @@ package buildah import ( "github.com/opencontainers/runtime-tools/generate" + selinux "github.com/opencontainers/selinux/go-selinux" ) func setupSelinux(g *generate.Generator, processLabel, mountLabel string) { - g.SetProcessSelinuxLabel(processLabel) - g.SetLinuxMountLabel(mountLabel) + if processLabel != "" && selinux.GetEnabled() { + g.SetProcessSelinuxLabel(processLabel) + g.SetLinuxMountLabel(mountLabel) + } } diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 7f3bbaef4..698d79a81 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -1,13 +1,11 @@ package util import ( - "bufio" "fmt" "io" "net/url" "os" "path" - "strconv" "strings" "syscall" @@ -18,7 +16,6 @@ import ( "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/containers/storage" - "github.com/containers/storage/pkg/idtools" "github.com/docker/distribution/registry/api/errcode" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -299,92 +296,6 @@ func GetHostRootIDs(spec *specs.Spec) (uint32, uint32, error) { return GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, 0, 0) } -// getHostIDMappings reads mappings from the named node under /proc. -func getHostIDMappings(path string) ([]specs.LinuxIDMapping, error) { - var mappings []specs.LinuxIDMapping - f, err := os.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "error reading ID mappings from %q", path) - } - defer f.Close() - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) != 3 { - return nil, errors.Errorf("line %q from %q has %d fields, not 3", line, path, len(fields)) - } - cid, err := strconv.ParseUint(fields[0], 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "error parsing container ID value %q from line %q in %q", fields[0], line, path) - } - hid, err := strconv.ParseUint(fields[1], 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "error parsing host ID value %q from line %q in %q", fields[1], line, path) - } - size, err := strconv.ParseUint(fields[2], 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "error parsing size value %q from line %q in %q", fields[2], line, path) - } - mappings = append(mappings, specs.LinuxIDMapping{ContainerID: uint32(cid), HostID: uint32(hid), Size: uint32(size)}) - } - return mappings, nil -} - -// GetHostIDMappings reads mappings for the specified process (or the current -// process if pid is "self" or an empty string) from the kernel. -func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { - if pid == "" { - pid = "self" - } - uidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/uid_map", pid)) - if err != nil { - return nil, nil, err - } - gidmap, err := getHostIDMappings(fmt.Sprintf("/proc/%s/gid_map", pid)) - if err != nil { - return nil, nil, err - } - return uidmap, gidmap, nil -} - -// GetSubIDMappings reads mappings from /etc/subuid and /etc/subgid. -func GetSubIDMappings(user, group string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) { - mappings, err := idtools.NewIDMappings(user, group) - if err != nil { - return nil, nil, errors.Wrapf(err, "error reading subuid mappings for user %q and subgid mappings for group %q", user, group) - } - var uidmap, gidmap []specs.LinuxIDMapping - for _, m := range mappings.UIDs() { - uidmap = append(uidmap, specs.LinuxIDMapping{ - ContainerID: uint32(m.ContainerID), - HostID: uint32(m.HostID), - Size: uint32(m.Size), - }) - } - for _, m := range mappings.GIDs() { - gidmap = append(gidmap, specs.LinuxIDMapping{ - ContainerID: uint32(m.ContainerID), - HostID: uint32(m.HostID), - Size: uint32(m.Size), - }) - } - return uidmap, gidmap, nil -} - -// ParseIDMappings parses mapping triples. -func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap, error) { - uid, err := idtools.ParseIDMap(uidmap, "userns-uid-map") - if err != nil { - return nil, nil, err - } - gid, err := idtools.ParseIDMap(gidmap, "userns-gid-map") - if err != nil { - return nil, nil, err - } - return uid, gid, nil -} - // GetPolicyContext sets up, initializes and returns a new context for the specified policy func GetPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) { policy, err := signature.DefaultPolicy(ctx) diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index a77130acb..bec681e5c 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -8,7 +8,7 @@ github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 github.com/boltdb/bolt v1.3.1 -github.com/containers/storage v1.12.2 +github.com/containers/storage v1.12.3 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 github.com/docker/docker 54dddadc7d5d89fe0be88f76979f6f6ab0dede83 github.com/docker/docker-credential-helpers v0.6.1 diff --git a/vendor/github.com/containers/psgo/go.mod b/vendor/github.com/containers/psgo/go.mod new file mode 100644 index 000000000..dd671bbb0 --- /dev/null +++ b/vendor/github.com/containers/psgo/go.mod @@ -0,0 +1,11 @@ +module github.com/containers/psgo + +go 1.12 + +require ( + github.com/opencontainers/runc v0.0.0-20190425234816-dae70e8efea4 + github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 + github.com/sirupsen/logrus v0.0.0-20190403091019-9b3cdde74fbe + github.com/stretchr/testify v1.2.2 + golang.org/x/sys v0.0.0-20190425145619-16072639606e +) diff --git a/vendor/github.com/containers/psgo/psgo.go b/vendor/github.com/containers/psgo/psgo.go index e0f102735..f1936f917 100644 --- a/vendor/github.com/containers/psgo/psgo.go +++ b/vendor/github.com/containers/psgo/psgo.go @@ -93,7 +93,7 @@ func translateDescriptors(descriptors []string) ([]aixFormatDescriptor, error) { } } if !found { - return nil, errors.Wrapf(ErrUnkownDescriptor, "'%s'", d) + return nil, errors.Wrapf(ErrUnknownDescriptor, "'%s'", d) } } @@ -104,8 +104,8 @@ var ( // DefaultDescriptors is the `ps -ef` compatible default format. DefaultDescriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"} - // ErrUnkownDescriptor is returned when an unknown descriptor is parsed. - ErrUnkownDescriptor = errors.New("unknown descriptor") + // ErrUnknownDescriptor is returned when an unknown descriptor is parsed. + ErrUnknownDescriptor = errors.New("unknown descriptor") aixFormatDescriptors = []aixFormatDescriptor{ { @@ -327,7 +327,10 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, dataErr = err return } - unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS) + if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil { + dataErr = err + return + } // extract all pids mentioned in pid's mount namespace pids, err := proc.GetPIDs() diff --git a/vendor/github.com/containers/storage/containers_ffjson.go b/vendor/github.com/containers/storage/containers_ffjson.go index 40b912bb3..aef6becfe 100644 --- a/vendor/github.com/containers/storage/containers_ffjson.go +++ b/vendor/github.com/containers/storage/containers_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: ./containers.go +// source: containers.go package storage diff --git a/vendor/github.com/containers/storage/drivers/copy/copy.go b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go index bcbc61284..d614b78fc 100644 --- a/vendor/github.com/containers/storage/drivers/copy/copy.go +++ b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go @@ -1,4 +1,4 @@ -// +build linux +// +build cgo package copy @@ -153,8 +153,8 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error { isHardlink := false - switch f.Mode() & os.ModeType { - case 0: // Regular file + switch mode := f.Mode(); { + case mode.IsRegular(): id := fileID{dev: stat.Dev, ino: stat.Ino} if copyMode == Hardlink { isHardlink = true @@ -172,12 +172,12 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error { copiedFiles[id] = dstPath } - case os.ModeDir: + case mode.IsDir(): if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) { return err } - case os.ModeSymlink: + case mode&os.ModeSymlink != 0: link, err := os.Readlink(srcPath) if err != nil { return err @@ -187,14 +187,15 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error { return err } - case os.ModeNamedPipe: + case mode&os.ModeNamedPipe != 0: fallthrough - case os.ModeSocket: + + case mode&os.ModeSocket != 0: if err := unix.Mkfifo(dstPath, stat.Mode); err != nil { return err } - case os.ModeDevice: + case mode&os.ModeDevice != 0: if rsystem.RunningInUserNS() { // cannot create a device if running in user namespace return nil @@ -204,7 +205,7 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error { } default: - return fmt.Errorf("unknown file type for %s", srcPath) + return fmt.Errorf("unknown file type with mode %v for %s", mode, srcPath) } // Everything below is copying metadata from src to dst. All this metadata diff --git a/vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go b/vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go new file mode 100644 index 000000000..4d44f2f35 --- /dev/null +++ b/vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go @@ -0,0 +1,19 @@ +// +build !linux !cgo + +package copy + +import "github.com/containers/storage/pkg/chrootarchive" + +// Mode indicates whether to use hardlink or copy content +type Mode int + +const ( + // Content creates a new file, and copies the content of the file + Content Mode = iota +) + +// DirCopy copies or hardlinks the contents of one directory to another, +// properly handling soft links +func DirCopy(srcDir, dstDir string, _ Mode, _ bool) error { + return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir) +} diff --git a/vendor/github.com/containers/storage/drivers/devmapper/device_setup.go b/vendor/github.com/containers/storage/drivers/devmapper/device_setup.go index 58abca477..f63845252 100644 --- a/vendor/github.com/containers/storage/drivers/devmapper/device_setup.go +++ b/vendor/github.com/containers/storage/drivers/devmapper/device_setup.go @@ -119,10 +119,17 @@ func checkDevHasFS(dev string) error { } func verifyBlockDevice(dev string, force bool) error { - if err := checkDevAvailable(dev); err != nil { + realPath, err := filepath.Abs(dev) + if err != nil { + return errors.Errorf("unable to get absolute path for %s: %s", dev, err) + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return errors.Errorf("failed to canonicalise path for %s: %s", dev, err) + } + if err := checkDevAvailable(realPath); err != nil { return err } - if err := checkDevInVG(dev); err != nil { + if err := checkDevInVG(realPath); err != nil { return err } @@ -130,7 +137,7 @@ func verifyBlockDevice(dev string, force bool) error { return nil } - if err := checkDevHasFS(dev); err != nil { + if err := checkDevHasFS(realPath); err != nil { return err } return nil diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index 657d9b3ce..5d667d8c6 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -16,7 +16,7 @@ import ( "sync" "syscall" - "github.com/containers/storage/drivers" + graphdriver "github.com/containers/storage/drivers" "github.com/containers/storage/drivers/overlayutils" "github.com/containers/storage/drivers/quota" "github.com/containers/storage/pkg/archive" @@ -320,6 +320,8 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI mergedDir := filepath.Join(layerDir, "merged") lower1Dir := filepath.Join(layerDir, "lower1") lower2Dir := filepath.Join(layerDir, "lower2") + upperDir := filepath.Join(layerDir, "upper") + workDir := filepath.Join(layerDir, "work") defer func() { // Permitted to fail, since the various subdirectories // can be empty or not even there, and the home might @@ -331,7 +333,9 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI _ = idtools.MkdirAs(mergedDir, 0700, rootUID, rootGID) _ = idtools.MkdirAs(lower1Dir, 0700, rootUID, rootGID) _ = idtools.MkdirAs(lower2Dir, 0700, rootUID, rootGID) - flags := fmt.Sprintf("lowerdir=%s:%s", lower1Dir, lower2Dir) + _ = idtools.MkdirAs(upperDir, 0700, rootUID, rootGID) + _ = idtools.MkdirAs(workDir, 0700, rootUID, rootGID) + flags := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", lower1Dir, lower2Dir, upperDir, workDir) if len(flags) < unix.Getpagesize() { err := mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags) if err == nil { @@ -341,7 +345,7 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI logrus.Debugf("overlay test mount with multiple lowers failed %v", err) } } - flags = fmt.Sprintf("lowerdir=%s", lower1Dir) + flags = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower1Dir, upperDir, workDir) if len(flags) < unix.Getpagesize() { err := mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags) if err == nil { @@ -677,6 +681,40 @@ func (d *Driver) Remove(id string) error { return nil } +// recreateSymlinks goes through the driver's home directory and checks if the diff directory +// under each layer has a symlink created for it under the linkDir. If the symlink does not +// exist, it creates them +func (d *Driver) recreateSymlinks() error { + // List all the directories under the home directory + dirs, err := ioutil.ReadDir(d.home) + if err != nil { + return fmt.Errorf("error reading driver home directory %q: %v", d.home, err) + } + for _, dir := range dirs { + // Skip over the linkDir + if dir.Name() == linkDir || dir.Mode().IsRegular() { + continue + } + // Read the "link" file under each layer to get the name of the symlink + data, err := ioutil.ReadFile(path.Join(d.dir(dir.Name()), "link")) + if err != nil { + return fmt.Errorf("error reading name of symlink for %q: %v", dir, err) + } + linkPath := path.Join(d.home, linkDir, strings.Trim(string(data), "\n")) + // Check if the symlink exists, and if it doesn't create it again with the name we + // got from the "link" file + _, err = os.Stat(linkPath) + if err != nil && os.IsNotExist(err) { + if err := os.Symlink(path.Join("..", dir.Name(), "diff"), linkPath); err != nil { + return err + } + } else if err != nil { + return fmt.Errorf("error trying to stat %q: %v", linkPath, err) + } + } + return nil +} + // Get creates and mounts the required file system for the given id and returns the mount path. func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) { return d.get(id, false, options) @@ -732,7 +770,16 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO } lower = "" } - if lower == "" { + // if it is a "not found" error, that means the symlinks were lost in a sudden reboot + // so call the recreateSymlinks function to go through all the layer dirs and recreate + // the symlinks with the name from their respective "link" files + if lower == "" && os.IsNotExist(err) { + logrus.Warnf("Can't stat lower layer %q because it does not exist. Going through storage to recreate the missing symlinks.", newpath) + if err := d.recreateSymlinks(); err != nil { + return "", fmt.Errorf("error recreating the missing symlinks: %v", err) + } + lower = newpath + } else if lower == "" { return "", fmt.Errorf("Can't stat lower layer %q: %v", newpath, err) } } else { @@ -796,7 +843,17 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO mountProgram := exec.Command(d.options.mountProgram, "-o", label, target) mountProgram.Dir = d.home - return mountProgram.Run() + var b bytes.Buffer + mountProgram.Stderr = &b + err := mountProgram.Run() + if err != nil { + output := b.String() + if output == "" { + output = "<stderr empty>" + } + return errors.Wrapf(err, "using mount program %s: %s", d.options.mountProgram, output) + } + return nil } } else if len(mountData) > pageSize { //FIXME: We need to figure out to get this to work with additional stores diff --git a/vendor/github.com/containers/storage/images_ffjson.go b/vendor/github.com/containers/storage/images_ffjson.go index 539acfe93..6b40ebd59 100644 --- a/vendor/github.com/containers/storage/images_ffjson.go +++ b/vendor/github.com/containers/storage/images_ffjson.go @@ -1,5 +1,5 @@ // Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT. -// source: ./images.go +// source: images.go package storage diff --git a/vendor/github.com/containers/storage/lockfile.go b/vendor/github.com/containers/storage/lockfile.go index 3a1befcbe..ed8753337 100644 --- a/vendor/github.com/containers/storage/lockfile.go +++ b/vendor/github.com/containers/storage/lockfile.go @@ -58,8 +58,17 @@ func GetROLockfile(path string) (Locker, error) { return getLockfile(path, true) } -// getLockfile is a helper for GetLockfile and GetROLockfile and returns Locker -// based on the path and read-only property. +// getLockfile returns a Locker object, possibly (depending on the platform) +// working inter-process, and associated with the specified path. +// +// If ro, the lock is a read-write lock and the returned Locker should correspond to the +// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock, +// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation. +// +// WARNING: +// - The lock may or MAY NOT be inter-process. +// - There may or MAY NOT be an actual object on the filesystem created for the specified path. +// - Even if ro, the lock MAY be exclusive. func getLockfile(path string, ro bool) (Locker, error) { lockfilesLock.Lock() defer lockfilesLock.Unlock() @@ -79,7 +88,7 @@ func getLockfile(path string, ro bool) (Locker, error) { } return locker, nil } - locker, err := getLockFile(path, ro) // platform dependent locker + locker, err := createLockerForPath(path, ro) // platform-dependent locker if err != nil { return nil, err } diff --git a/vendor/github.com/containers/storage/lockfile_unix.go b/vendor/github.com/containers/storage/lockfile_unix.go index a9dc64122..8e0f22cb5 100644 --- a/vendor/github.com/containers/storage/lockfile_unix.go +++ b/vendor/github.com/containers/storage/lockfile_unix.go @@ -13,18 +13,51 @@ import ( "golang.org/x/sys/unix" ) -func getLockFile(path string, ro bool) (Locker, error) { - var fd int - var err error +type lockfile struct { + // rwMutex serializes concurrent reader-writer acquisitions in the same process space + rwMutex *sync.RWMutex + // stateMutex is used to synchronize concurrent accesses to the state below + stateMutex *sync.Mutex + counter int64 + file string + fd uintptr + lw string + locktype int16 + locked bool + ro bool +} + +// openLock opens the file at path and returns the corresponding file +// descriptor. Note that the path is opened read-only when ro is set. If ro +// is unset, openLock will open the path read-write and create the file if +// necessary. +func openLock(path string, ro bool) (int, error) { if ro { - fd, err = unix.Open(path, os.O_RDONLY, 0) - } else { - fd, err = unix.Open(path, os.O_RDWR|os.O_CREATE, unix.S_IRUSR|unix.S_IWUSR) + return unix.Open(path, os.O_RDONLY, 0) } + return unix.Open(path, os.O_RDWR|os.O_CREATE, unix.S_IRUSR|unix.S_IWUSR) +} + +// createLockerForPath returns a Locker object, possibly (depending on the platform) +// working inter-process and associated with the specified path. +// +// This function will be called at most once for each path value within a single process. +// +// If ro, the lock is a read-write lock and the returned Locker should correspond to the +// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock, +// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation. +// +// WARNING: +// - The lock may or MAY NOT be inter-process. +// - There may or MAY NOT be an actual object on the filesystem created for the specified path. +// - Even if ro, the lock MAY be exclusive. +func createLockerForPath(path string, ro bool) (Locker, error) { + // Check if we can open the lock. + fd, err := openLock(path, ro) if err != nil { return nil, errors.Wrapf(err, "error opening %q", path) } - unix.CloseOnExec(fd) + unix.Close(fd) locktype := unix.F_WRLCK if ro { @@ -34,27 +67,12 @@ func getLockFile(path string, ro bool) (Locker, error) { stateMutex: &sync.Mutex{}, rwMutex: &sync.RWMutex{}, file: path, - fd: uintptr(fd), lw: stringid.GenerateRandomID(), locktype: int16(locktype), locked: false, ro: ro}, nil } -type lockfile struct { - // rwMutex serializes concurrent reader-writer acquisitions in the same process space - rwMutex *sync.RWMutex - // stateMutex is used to synchronize concurrent accesses to the state below - stateMutex *sync.Mutex - counter int64 - file string - fd uintptr - lw string - locktype int16 - locked bool - ro bool -} - // lock locks the lockfile via FCTNL(2) based on the specified type and // command. func (l *lockfile) lock(l_type int16) { @@ -63,7 +81,6 @@ func (l *lockfile) lock(l_type int16) { Whence: int16(os.SEEK_SET), Start: 0, Len: 0, - Pid: int32(os.Getpid()), } switch l_type { case unix.F_RDLCK: @@ -74,7 +91,16 @@ func (l *lockfile) lock(l_type int16) { panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", l_type)) } l.stateMutex.Lock() + defer l.stateMutex.Unlock() if l.counter == 0 { + // If we're the first reference on the lock, we need to open the file again. + fd, err := openLock(l.file, l.ro) + if err != nil { + panic(fmt.Sprintf("error opening %q", l.file)) + } + unix.CloseOnExec(fd) + l.fd = uintptr(fd) + // Optimization: only use the (expensive) fcntl syscall when // the counter is 0. In this case, we're either the first // reader lock or a writer lock. @@ -85,7 +111,6 @@ func (l *lockfile) lock(l_type int16) { l.locktype = l_type l.locked = true l.counter++ - l.stateMutex.Unlock() } // Lock locks the lockfile as a writer. Note that RLock() will be called if @@ -133,6 +158,8 @@ func (l *lockfile) Unlock() { for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil { time.Sleep(10 * time.Millisecond) } + // Close the file descriptor on the last unlock. + unix.Close(int(l.fd)) } if l.locktype == unix.F_RDLCK { l.rwMutex.RUnlock() diff --git a/vendor/github.com/containers/storage/lockfile_windows.go b/vendor/github.com/containers/storage/lockfile_windows.go index a3821bfeb..c02069495 100644 --- a/vendor/github.com/containers/storage/lockfile_windows.go +++ b/vendor/github.com/containers/storage/lockfile_windows.go @@ -8,7 +8,20 @@ import ( "time" ) -func getLockFile(path string, ro bool) (Locker, error) { +// createLockerForPath returns a Locker object, possibly (depending on the platform) +// working inter-process and associated with the specified path. +// +// This function will be called at most once for each path value within a single process. +// +// If ro, the lock is a read-write lock and the returned Locker should correspond to the +// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock, +// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation. +// +// WARNING: +// - The lock may or MAY NOT be inter-process. +// - There may or MAY NOT be an actual object on the filesystem created for the specified path. +// - Even if ro, the lock MAY be exclusive. +func createLockerForPath(path string, ro bool) (Locker, error) { return &lockfile{locked: false}, nil } diff --git a/vendor/github.com/containers/storage/pkg/idtools/parser.go b/vendor/github.com/containers/storage/pkg/idtools/parser.go index c56aa86a2..86f98f16e 100644 --- a/vendor/github.com/containers/storage/pkg/idtools/parser.go +++ b/vendor/github.com/containers/storage/pkg/idtools/parser.go @@ -2,6 +2,8 @@ package idtools import ( "fmt" + "math" + "math/bits" "strconv" "strings" ) @@ -31,10 +33,11 @@ func parseTriple(spec []string) (container, host, size uint32, err error) { // ParseIDMap parses idmap triples from string. func ParseIDMap(mapSpec []string, mapSetting string) (idmap []IDMap, err error) { + stdErr := fmt.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) for _, idMapSpec := range mapSpec { idSpec := strings.Fields(strings.Map(nonDigitsToWhitespace, idMapSpec)) if len(idSpec)%3 != 0 { - return nil, fmt.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + return nil, stdErr } for i := range idSpec { if i%3 != 0 { @@ -42,7 +45,11 @@ func ParseIDMap(mapSpec []string, mapSetting string) (idmap []IDMap, err error) } cid, hid, size, err := parseTriple(idSpec[i : i+3]) if err != nil { - return nil, fmt.Errorf("error initializing ID mappings: %s setting is malformed", mapSetting) + return nil, stdErr + } + // Avoid possible integer overflow on 32bit builds + if bits.UintSize == 32 && (cid > math.MaxInt32 || hid > math.MaxInt32 || size > math.MaxInt32) { + return nil, stdErr } mapping := IDMap{ ContainerID: int(cid), diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index 7e39e3959..27b00f6fe 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -460,6 +460,9 @@ type Store interface { // Version returns version information, in the form of key-value pairs, from // the storage package. Version() ([][2]string, error) + + // GetDigestLock returns digest-specific Locker. + GetDigestLock(digest.Digest) (Locker, error) } // IDMappingOptions are used for specifying how ID mapping should be set up for @@ -529,6 +532,7 @@ type store struct { imageStore ImageStore roImageStores []ROImageStore containerStore ContainerStore + digestLockRoot string } // GetStore attempts to find an already-created Store object matching the @@ -698,9 +702,20 @@ func (s *store) load() error { return err } s.containerStore = rcs + + s.digestLockRoot = filepath.Join(s.runRoot, driverPrefix+"locks") + if err := os.MkdirAll(s.digestLockRoot, 0700); err != nil { + return err + } + return nil } +// GetDigestLock returns a digest-specific Locker. +func (s *store) GetDigestLock(d digest.Digest) (Locker, error) { + return GetLockfile(filepath.Join(s.digestLockRoot, d.String())) +} + func (s *store) getGraphDriver() (drivers.Driver, error) { if s.graphDriver != nil { return s.graphDriver, nil @@ -1023,8 +1038,9 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, crea return reflect.DeepEqual(layer.UIDMap, options.UIDMap) && reflect.DeepEqual(layer.GIDMap, options.GIDMap) } var layer, parentLayer *Layer + allStores := append([]ROLayerStore{rlstore}, lstores...) // Locate the image's top layer and its parent, if it has one. - for _, s := range append([]ROLayerStore{rlstore}, lstores...) { + for _, s := range allStores { store := s if store != rlstore { store.Lock() @@ -1041,10 +1057,13 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, crea // We want the layer's parent, too, if it has one. var cParentLayer *Layer if cLayer.Parent != "" { - // Its parent should be around here, somewhere. - if cParentLayer, err = store.Get(cLayer.Parent); err != nil { - // Nope, couldn't find it. We're not going to be able - // to diff this one properly. + // Its parent should be in one of the stores, somewhere. + for _, ps := range allStores { + if cParentLayer, err = ps.Get(cLayer.Parent); err == nil { + break + } + } + if cParentLayer == nil { continue } } diff --git a/vendor/github.com/containers/storage/utils.go b/vendor/github.com/containers/storage/utils.go index e74956c9e..6c9f163a3 100644 --- a/vendor/github.com/containers/storage/utils.go +++ b/vendor/github.com/containers/storage/utils.go @@ -6,6 +6,7 @@ import ( "os/exec" "os/user" "path/filepath" + "strconv" "strings" "github.com/BurntSushi/toml" @@ -73,7 +74,7 @@ func GetRootlessRuntimeDir(rootlessUid int) (string, error) { if runtimeDir == "" { tmpDir := fmt.Sprintf("/run/user/%d", rootlessUid) st, err := system.Stat(tmpDir) - if err == nil && int(st.UID()) == os.Getuid() && st.Mode() == 0700 { + if err == nil && int(st.UID()) == os.Getuid() && st.Mode()&0700 == 0700 && st.Mode()&0066 == 0000 { return tmpDir, nil } } @@ -158,6 +159,21 @@ func getTomlStorage(storeOptions *StoreOptions) *tomlConfig { return config } +func getRootlessUID() int { + uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") + if uidEnv != "" { + u, _ := strconv.Atoi(uidEnv) + return u + } + return os.Geteuid() +} + +// DefaultStoreOptionsAutoDetectUID returns the default storage ops for containers +func DefaultStoreOptionsAutoDetectUID() (StoreOptions, error) { + uid := getRootlessUID() + return DefaultStoreOptions(uid != 0, uid) +} + // DefaultStoreOptions returns the default storage ops for containers func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) { var ( @@ -166,14 +182,14 @@ func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) { err error ) storageOpts := defaultStoreOptions - if rootless { + if rootless && rootlessUid != 0 { storageOpts, err = getRootlessStorageOpts(rootlessUid) if err != nil { return storageOpts, err } } - storageConf, err := DefaultConfigFile(rootless) + storageConf, err := DefaultConfigFile(rootless && rootlessUid != 0) if err != nil { return storageOpts, err } @@ -188,7 +204,7 @@ func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) { return storageOpts, errors.Wrapf(err, "cannot stat %s", storageConf) } - if rootless { + if rootless && rootlessUid != 0 { if err == nil { // If the file did not specify a graphroot or runroot, // set sane defaults so we don't try and use root-owned diff --git a/vendor/github.com/coreos/go-systemd/journal/journal.go b/vendor/github.com/coreos/go-systemd/journal/journal.go new file mode 100644 index 000000000..7f434990d --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/journal/journal.go @@ -0,0 +1,179 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package journal provides write bindings to the local systemd journal. +// It is implemented in pure Go and connects to the journal directly over its +// unix socket. +// +// To read from the journal, see the "sdjournal" package, which wraps the +// sd-journal a C API. +// +// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html +package journal + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "strconv" + "strings" + "syscall" +) + +// Priority of a journal message +type Priority int + +const ( + PriEmerg Priority = iota + PriAlert + PriCrit + PriErr + PriWarning + PriNotice + PriInfo + PriDebug +) + +var conn net.Conn + +func init() { + var err error + conn, err = net.Dial("unixgram", "/run/systemd/journal/socket") + if err != nil { + conn = nil + } +} + +// Enabled returns true if the local systemd journal is available for logging +func Enabled() bool { + return conn != nil +} + +// Send a message to the local systemd journal. vars is a map of journald +// fields to values. Fields must be composed of uppercase letters, numbers, +// and underscores, but must not start with an underscore. Within these +// restrictions, any arbitrary field name may be used. Some names have special +// significance: see the journalctl documentation +// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) +// for more details. vars may be nil. +func Send(message string, priority Priority, vars map[string]string) error { + if conn == nil { + return journalError("could not connect to journald socket") + } + + data := new(bytes.Buffer) + appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) + appendVariable(data, "MESSAGE", message) + for k, v := range vars { + appendVariable(data, k, v) + } + + _, err := io.Copy(conn, data) + if err != nil && isSocketSpaceError(err) { + file, err := tempFd() + if err != nil { + return journalError(err.Error()) + } + defer file.Close() + _, err = io.Copy(file, data) + if err != nil { + return journalError(err.Error()) + } + + rights := syscall.UnixRights(int(file.Fd())) + + /* this connection should always be a UnixConn, but better safe than sorry */ + unixConn, ok := conn.(*net.UnixConn) + if !ok { + return journalError("can't send file through non-Unix connection") + } + unixConn.WriteMsgUnix([]byte{}, rights, nil) + } else if err != nil { + return journalError(err.Error()) + } + return nil +} + +// Print prints a message to the local systemd journal using Send(). +func Print(priority Priority, format string, a ...interface{}) error { + return Send(fmt.Sprintf(format, a...), priority, nil) +} + +func appendVariable(w io.Writer, name, value string) { + if !validVarName(name) { + journalError("variable name contains invalid character, ignoring") + } + if strings.ContainsRune(value, '\n') { + /* When the value contains a newline, we write: + * - the variable name, followed by a newline + * - the size (in 64bit little endian format) + * - the data, followed by a newline + */ + fmt.Fprintln(w, name) + binary.Write(w, binary.LittleEndian, uint64(len(value))) + fmt.Fprintln(w, value) + } else { + /* just write the variable and value all on one line */ + fmt.Fprintf(w, "%s=%s\n", name, value) + } +} + +func validVarName(name string) bool { + /* The variable name must be in uppercase and consist only of characters, + * numbers and underscores, and may not begin with an underscore. (from the docs) + */ + + valid := name[0] != '_' + for _, c := range name { + valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' + } + return valid +} + +func isSocketSpaceError(err error) bool { + opErr, ok := err.(*net.OpError) + if !ok { + return false + } + + sysErr, ok := opErr.Err.(syscall.Errno) + if !ok { + return false + } + + return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS +} + +func tempFd() (*os.File, error) { + file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") + if err != nil { + return nil, err + } + syscall.Unlink(file.Name()) + if err != nil { + return nil, err + } + return file, nil +} + +func journalError(s string) error { + s = "journal error: " + s + fmt.Fprintln(os.Stderr, s) + return errors.New(s) +} diff --git a/vendor/github.com/coreos/go-systemd/sdjournal/functions.go b/vendor/github.com/coreos/go-systemd/sdjournal/functions.go new file mode 100644 index 000000000..e132369c1 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/sdjournal/functions.go @@ -0,0 +1,66 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdjournal + +import ( + "github.com/coreos/pkg/dlopen" + "sync" + "unsafe" +) + +var ( + // lazy initialized + libsystemdHandle *dlopen.LibHandle + + libsystemdMutex = &sync.Mutex{} + libsystemdFunctions = map[string]unsafe.Pointer{} + libsystemdNames = []string{ + // systemd < 209 + "libsystemd-journal.so.0", + "libsystemd-journal.so", + + // systemd >= 209 merged libsystemd-journal into libsystemd proper + "libsystemd.so.0", + "libsystemd.so", + } +) + +func getFunction(name string) (unsafe.Pointer, error) { + libsystemdMutex.Lock() + defer libsystemdMutex.Unlock() + + if libsystemdHandle == nil { + h, err := dlopen.GetHandle(libsystemdNames) + if err != nil { + return nil, err + } + + libsystemdHandle = h + } + + f, ok := libsystemdFunctions[name] + if !ok { + var err error + f, err = libsystemdHandle.GetSymbolPointer(name) + if err != nil { + return nil, err + } + + libsystemdFunctions[name] = f + } + + return f, nil +} diff --git a/vendor/github.com/coreos/go-systemd/sdjournal/journal.go b/vendor/github.com/coreos/go-systemd/sdjournal/journal.go new file mode 100644 index 000000000..b00d606c1 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/sdjournal/journal.go @@ -0,0 +1,1024 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package sdjournal provides a low-level Go interface to the +// systemd journal wrapped around the sd-journal C API. +// +// All public read methods map closely to the sd-journal API functions. See the +// sd-journal.h documentation[1] for information about each function. +// +// To write to the journal, see the pure-Go "journal" package +// +// [1] http://www.freedesktop.org/software/systemd/man/sd-journal.html +package sdjournal + +// #include <systemd/sd-journal.h> +// #include <systemd/sd-id128.h> +// #include <stdlib.h> +// #include <syslog.h> +// +// int +// my_sd_journal_open(void *f, sd_journal **ret, int flags) +// { +// int (*sd_journal_open)(sd_journal **, int); +// +// sd_journal_open = f; +// return sd_journal_open(ret, flags); +// } +// +// int +// my_sd_journal_open_directory(void *f, sd_journal **ret, const char *path, int flags) +// { +// int (*sd_journal_open_directory)(sd_journal **, const char *, int); +// +// sd_journal_open_directory = f; +// return sd_journal_open_directory(ret, path, flags); +// } +// +// void +// my_sd_journal_close(void *f, sd_journal *j) +// { +// int (*sd_journal_close)(sd_journal *); +// +// sd_journal_close = f; +// sd_journal_close(j); +// } +// +// int +// my_sd_journal_get_usage(void *f, sd_journal *j, uint64_t *bytes) +// { +// int (*sd_journal_get_usage)(sd_journal *, uint64_t *); +// +// sd_journal_get_usage = f; +// return sd_journal_get_usage(j, bytes); +// } +// +// int +// my_sd_journal_add_match(void *f, sd_journal *j, const void *data, size_t size) +// { +// int (*sd_journal_add_match)(sd_journal *, const void *, size_t); +// +// sd_journal_add_match = f; +// return sd_journal_add_match(j, data, size); +// } +// +// int +// my_sd_journal_add_disjunction(void *f, sd_journal *j) +// { +// int (*sd_journal_add_disjunction)(sd_journal *); +// +// sd_journal_add_disjunction = f; +// return sd_journal_add_disjunction(j); +// } +// +// int +// my_sd_journal_add_conjunction(void *f, sd_journal *j) +// { +// int (*sd_journal_add_conjunction)(sd_journal *); +// +// sd_journal_add_conjunction = f; +// return sd_journal_add_conjunction(j); +// } +// +// void +// my_sd_journal_flush_matches(void *f, sd_journal *j) +// { +// int (*sd_journal_flush_matches)(sd_journal *); +// +// sd_journal_flush_matches = f; +// sd_journal_flush_matches(j); +// } +// +// int +// my_sd_journal_next(void *f, sd_journal *j) +// { +// int (*sd_journal_next)(sd_journal *); +// +// sd_journal_next = f; +// return sd_journal_next(j); +// } +// +// int +// my_sd_journal_next_skip(void *f, sd_journal *j, uint64_t skip) +// { +// int (*sd_journal_next_skip)(sd_journal *, uint64_t); +// +// sd_journal_next_skip = f; +// return sd_journal_next_skip(j, skip); +// } +// +// int +// my_sd_journal_previous(void *f, sd_journal *j) +// { +// int (*sd_journal_previous)(sd_journal *); +// +// sd_journal_previous = f; +// return sd_journal_previous(j); +// } +// +// int +// my_sd_journal_previous_skip(void *f, sd_journal *j, uint64_t skip) +// { +// int (*sd_journal_previous_skip)(sd_journal *, uint64_t); +// +// sd_journal_previous_skip = f; +// return sd_journal_previous_skip(j, skip); +// } +// +// int +// my_sd_journal_get_data(void *f, sd_journal *j, const char *field, const void **data, size_t *length) +// { +// int (*sd_journal_get_data)(sd_journal *, const char *, const void **, size_t *); +// +// sd_journal_get_data = f; +// return sd_journal_get_data(j, field, data, length); +// } +// +// int +// my_sd_journal_set_data_threshold(void *f, sd_journal *j, size_t sz) +// { +// int (*sd_journal_set_data_threshold)(sd_journal *, size_t); +// +// sd_journal_set_data_threshold = f; +// return sd_journal_set_data_threshold(j, sz); +// } +// +// int +// my_sd_journal_get_cursor(void *f, sd_journal *j, char **cursor) +// { +// int (*sd_journal_get_cursor)(sd_journal *, char **); +// +// sd_journal_get_cursor = f; +// return sd_journal_get_cursor(j, cursor); +// } +// +// int +// my_sd_journal_test_cursor(void *f, sd_journal *j, const char *cursor) +// { +// int (*sd_journal_test_cursor)(sd_journal *, const char *); +// +// sd_journal_test_cursor = f; +// return sd_journal_test_cursor(j, cursor); +// } +// +// int +// my_sd_journal_get_realtime_usec(void *f, sd_journal *j, uint64_t *usec) +// { +// int (*sd_journal_get_realtime_usec)(sd_journal *, uint64_t *); +// +// sd_journal_get_realtime_usec = f; +// return sd_journal_get_realtime_usec(j, usec); +// } +// +// int +// my_sd_journal_get_monotonic_usec(void *f, sd_journal *j, uint64_t *usec, sd_id128_t *boot_id) +// { +// int (*sd_journal_get_monotonic_usec)(sd_journal *, uint64_t *, sd_id128_t *); +// +// sd_journal_get_monotonic_usec = f; +// return sd_journal_get_monotonic_usec(j, usec, boot_id); +// } +// +// int +// my_sd_journal_seek_head(void *f, sd_journal *j) +// { +// int (*sd_journal_seek_head)(sd_journal *); +// +// sd_journal_seek_head = f; +// return sd_journal_seek_head(j); +// } +// +// int +// my_sd_journal_seek_tail(void *f, sd_journal *j) +// { +// int (*sd_journal_seek_tail)(sd_journal *); +// +// sd_journal_seek_tail = f; +// return sd_journal_seek_tail(j); +// } +// +// +// int +// my_sd_journal_seek_cursor(void *f, sd_journal *j, const char *cursor) +// { +// int (*sd_journal_seek_cursor)(sd_journal *, const char *); +// +// sd_journal_seek_cursor = f; +// return sd_journal_seek_cursor(j, cursor); +// } +// +// int +// my_sd_journal_seek_realtime_usec(void *f, sd_journal *j, uint64_t usec) +// { +// int (*sd_journal_seek_realtime_usec)(sd_journal *, uint64_t); +// +// sd_journal_seek_realtime_usec = f; +// return sd_journal_seek_realtime_usec(j, usec); +// } +// +// int +// my_sd_journal_wait(void *f, sd_journal *j, uint64_t timeout_usec) +// { +// int (*sd_journal_wait)(sd_journal *, uint64_t); +// +// sd_journal_wait = f; +// return sd_journal_wait(j, timeout_usec); +// } +// +// void +// my_sd_journal_restart_data(void *f, sd_journal *j) +// { +// void (*sd_journal_restart_data)(sd_journal *); +// +// sd_journal_restart_data = f; +// sd_journal_restart_data(j); +// } +// +// int +// my_sd_journal_enumerate_data(void *f, sd_journal *j, const void **data, size_t *length) +// { +// int (*sd_journal_enumerate_data)(sd_journal *, const void **, size_t *); +// +// sd_journal_enumerate_data = f; +// return sd_journal_enumerate_data(j, data, length); +// } +// +// int +// my_sd_journal_query_unique(void *f, sd_journal *j, const char *field) +// { +// int(*sd_journal_query_unique)(sd_journal *, const char *); +// +// sd_journal_query_unique = f; +// return sd_journal_query_unique(j, field); +// } +// +// int +// my_sd_journal_enumerate_unique(void *f, sd_journal *j, const void **data, size_t *length) +// { +// int(*sd_journal_enumerate_unique)(sd_journal *, const void **, size_t *); +// +// sd_journal_enumerate_unique = f; +// return sd_journal_enumerate_unique(j, data, length); +// } +// +// void +// my_sd_journal_restart_unique(void *f, sd_journal *j) +// { +// void(*sd_journal_restart_unique)(sd_journal *); +// +// sd_journal_restart_unique = f; +// sd_journal_restart_unique(j); +// } +// +import "C" +import ( + "bytes" + "fmt" + "strings" + "sync" + "syscall" + "time" + "unsafe" +) + +// Journal entry field strings which correspond to: +// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html +const ( + // User Journal Fields + SD_JOURNAL_FIELD_MESSAGE = "MESSAGE" + SD_JOURNAL_FIELD_MESSAGE_ID = "MESSAGE_ID" + SD_JOURNAL_FIELD_PRIORITY = "PRIORITY" + SD_JOURNAL_FIELD_CODE_FILE = "CODE_FILE" + SD_JOURNAL_FIELD_CODE_LINE = "CODE_LINE" + SD_JOURNAL_FIELD_CODE_FUNC = "CODE_FUNC" + SD_JOURNAL_FIELD_ERRNO = "ERRNO" + SD_JOURNAL_FIELD_SYSLOG_FACILITY = "SYSLOG_FACILITY" + SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER" + SD_JOURNAL_FIELD_SYSLOG_PID = "SYSLOG_PID" + + // Trusted Journal Fields + SD_JOURNAL_FIELD_PID = "_PID" + SD_JOURNAL_FIELD_UID = "_UID" + SD_JOURNAL_FIELD_GID = "_GID" + SD_JOURNAL_FIELD_COMM = "_COMM" + SD_JOURNAL_FIELD_EXE = "_EXE" + SD_JOURNAL_FIELD_CMDLINE = "_CMDLINE" + SD_JOURNAL_FIELD_CAP_EFFECTIVE = "_CAP_EFFECTIVE" + SD_JOURNAL_FIELD_AUDIT_SESSION = "_AUDIT_SESSION" + SD_JOURNAL_FIELD_AUDIT_LOGINUID = "_AUDIT_LOGINUID" + SD_JOURNAL_FIELD_SYSTEMD_CGROUP = "_SYSTEMD_CGROUP" + SD_JOURNAL_FIELD_SYSTEMD_SESSION = "_SYSTEMD_SESSION" + SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_USER_UNIT = "_SYSTEMD_USER_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_OWNER_UID = "_SYSTEMD_OWNER_UID" + SD_JOURNAL_FIELD_SYSTEMD_SLICE = "_SYSTEMD_SLICE" + SD_JOURNAL_FIELD_SELINUX_CONTEXT = "_SELINUX_CONTEXT" + SD_JOURNAL_FIELD_SOURCE_REALTIME_TIMESTAMP = "_SOURCE_REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_BOOT_ID = "_BOOT_ID" + SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID" + SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME" + SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT" + + // Address Fields + SD_JOURNAL_FIELD_CURSOR = "__CURSOR" + SD_JOURNAL_FIELD_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP" +) + +// Journal event constants +const ( + SD_JOURNAL_NOP = int(C.SD_JOURNAL_NOP) + SD_JOURNAL_APPEND = int(C.SD_JOURNAL_APPEND) + SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE) +) + +const ( + // IndefiniteWait is a sentinel value that can be passed to + // sdjournal.Wait() to signal an indefinite wait for new journal + // events. It is implemented as the maximum value for a time.Duration: + // https://github.com/golang/go/blob/e4dcf5c8c22d98ac9eac7b9b226596229624cb1d/src/time/time.go#L434 + IndefiniteWait time.Duration = 1<<63 - 1 +) + +// Journal is a Go wrapper of an sd_journal structure. +type Journal struct { + cjournal *C.sd_journal + mu sync.Mutex +} + +// JournalEntry represents all fields of a journal entry plus address fields. +type JournalEntry struct { + Fields map[string]string + Cursor string + RealtimeTimestamp uint64 + MonotonicTimestamp uint64 +} + +// Match is a convenience wrapper to describe filters supplied to AddMatch. +type Match struct { + Field string + Value string +} + +// String returns a string representation of a Match suitable for use with AddMatch. +func (m *Match) String() string { + return m.Field + "=" + m.Value +} + +// NewJournal returns a new Journal instance pointing to the local journal +func NewJournal() (j *Journal, err error) { + j = &Journal{} + + sd_journal_open, err := getFunction("sd_journal_open") + if err != nil { + return nil, err + } + + r := C.my_sd_journal_open(sd_journal_open, &j.cjournal, C.SD_JOURNAL_LOCAL_ONLY) + + if r < 0 { + return nil, fmt.Errorf("failed to open journal: %d", syscall.Errno(-r)) + } + + return j, nil +} + +// NewJournalFromDir returns a new Journal instance pointing to a journal residing +// in a given directory. The supplied path may be relative or absolute; if +// relative, it will be converted to an absolute path before being opened. +func NewJournalFromDir(path string) (j *Journal, err error) { + j = &Journal{} + + sd_journal_open_directory, err := getFunction("sd_journal_open_directory") + if err != nil { + return nil, err + } + + p := C.CString(path) + defer C.free(unsafe.Pointer(p)) + + r := C.my_sd_journal_open_directory(sd_journal_open_directory, &j.cjournal, p, 0) + if r < 0 { + return nil, fmt.Errorf("failed to open journal in directory %q: %d", path, syscall.Errno(-r)) + } + + return j, nil +} + +// Close closes a journal opened with NewJournal. +func (j *Journal) Close() error { + sd_journal_close, err := getFunction("sd_journal_close") + if err != nil { + return err + } + + j.mu.Lock() + C.my_sd_journal_close(sd_journal_close, j.cjournal) + j.mu.Unlock() + + return nil +} + +// AddMatch adds a match by which to filter the entries of the journal. +func (j *Journal) AddMatch(match string) error { + sd_journal_add_match, err := getFunction("sd_journal_add_match") + if err != nil { + return err + } + + m := C.CString(match) + defer C.free(unsafe.Pointer(m)) + + j.mu.Lock() + r := C.my_sd_journal_add_match(sd_journal_add_match, j.cjournal, unsafe.Pointer(m), C.size_t(len(match))) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add match: %d", syscall.Errno(-r)) + } + + return nil +} + +// AddDisjunction inserts a logical OR in the match list. +func (j *Journal) AddDisjunction() error { + sd_journal_add_disjunction, err := getFunction("sd_journal_add_disjunction") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_add_disjunction(sd_journal_add_disjunction, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add a disjunction in the match list: %d", syscall.Errno(-r)) + } + + return nil +} + +// AddConjunction inserts a logical AND in the match list. +func (j *Journal) AddConjunction() error { + sd_journal_add_conjunction, err := getFunction("sd_journal_add_conjunction") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_add_conjunction(sd_journal_add_conjunction, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add a conjunction in the match list: %d", syscall.Errno(-r)) + } + + return nil +} + +// FlushMatches flushes all matches, disjunctions and conjunctions. +func (j *Journal) FlushMatches() { + sd_journal_flush_matches, err := getFunction("sd_journal_flush_matches") + if err != nil { + return + } + + j.mu.Lock() + C.my_sd_journal_flush_matches(sd_journal_flush_matches, j.cjournal) + j.mu.Unlock() +} + +// Next advances the read pointer into the journal by one entry. +func (j *Journal) Next() (uint64, error) { + sd_journal_next, err := getFunction("sd_journal_next") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_next(sd_journal_next, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r)) + } + + return uint64(r), nil +} + +// NextSkip advances the read pointer by multiple entries at once, +// as specified by the skip parameter. +func (j *Journal) NextSkip(skip uint64) (uint64, error) { + sd_journal_next_skip, err := getFunction("sd_journal_next_skip") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_next_skip(sd_journal_next_skip, j.cjournal, C.uint64_t(skip)) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r)) + } + + return uint64(r), nil +} + +// Previous sets the read pointer into the journal back by one entry. +func (j *Journal) Previous() (uint64, error) { + sd_journal_previous, err := getFunction("sd_journal_previous") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_previous(sd_journal_previous, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r)) + } + + return uint64(r), nil +} + +// PreviousSkip sets back the read pointer by multiple entries at once, +// as specified by the skip parameter. +func (j *Journal) PreviousSkip(skip uint64) (uint64, error) { + sd_journal_previous_skip, err := getFunction("sd_journal_previous_skip") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_previous_skip(sd_journal_previous_skip, j.cjournal, C.uint64_t(skip)) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %d", syscall.Errno(-r)) + } + + return uint64(r), nil +} + +func (j *Journal) getData(field string) (unsafe.Pointer, C.int, error) { + sd_journal_get_data, err := getFunction("sd_journal_get_data") + if err != nil { + return nil, 0, err + } + + f := C.CString(field) + defer C.free(unsafe.Pointer(f)) + + var d unsafe.Pointer + var l C.size_t + + j.mu.Lock() + r := C.my_sd_journal_get_data(sd_journal_get_data, j.cjournal, f, &d, &l) + j.mu.Unlock() + + if r < 0 { + return nil, 0, fmt.Errorf("failed to read message: %d", syscall.Errno(-r)) + } + + return d, C.int(l), nil +} + +// GetData gets the data object associated with a specific field from the +// current journal entry. +func (j *Journal) GetData(field string) (string, error) { + d, l, err := j.getData(field) + if err != nil { + return "", err + } + + return C.GoStringN((*C.char)(d), l), nil +} + +// GetDataValue gets the data object associated with a specific field from the +// current journal entry, returning only the value of the object. +func (j *Journal) GetDataValue(field string) (string, error) { + val, err := j.GetData(field) + if err != nil { + return "", err + } + + return strings.SplitN(val, "=", 2)[1], nil +} + +// GetDataBytes gets the data object associated with a specific field from the +// current journal entry. +func (j *Journal) GetDataBytes(field string) ([]byte, error) { + d, l, err := j.getData(field) + if err != nil { + return nil, err + } + + return C.GoBytes(d, l), nil +} + +// GetDataValueBytes gets the data object associated with a specific field from the +// current journal entry, returning only the value of the object. +func (j *Journal) GetDataValueBytes(field string) ([]byte, error) { + val, err := j.GetDataBytes(field) + if err != nil { + return nil, err + } + + return bytes.SplitN(val, []byte("="), 2)[1], nil +} + +// GetEntry returns a full representation of a journal entry with +// all key-value pairs of data as well as address fields (cursor, realtime +// timestamp and monotonic timestamp) +func (j *Journal) GetEntry() (*JournalEntry, error) { + sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec") + if err != nil { + return nil, err + } + + sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return nil, err + } + + sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor") + if err != nil { + return nil, err + } + + sd_journal_restart_data, err := getFunction("sd_journal_restart_data") + if err != nil { + return nil, err + } + + sd_journal_enumerate_data, err := getFunction("sd_journal_enumerate_data") + if err != nil { + return nil, err + } + + j.mu.Lock() + defer j.mu.Unlock() + + var r C.int + entry := &JournalEntry{Fields: make(map[string]string)} + + var realtimeUsec C.uint64_t + r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec) + if r < 0 { + return nil, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r)) + } + + entry.RealtimeTimestamp = uint64(realtimeUsec) + + var monotonicUsec C.uint64_t + var boot_id C.sd_id128_t + + r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id) + if r < 0 { + return nil, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r)) + } + + entry.MonotonicTimestamp = uint64(monotonicUsec) + + var c *C.char + // since the pointer is mutated by sd_journal_get_cursor, need to wait + // until after the call to free the memory + r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c) + defer C.free(unsafe.Pointer(c)) + if r < 0 { + return nil, fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r)) + } + + entry.Cursor = C.GoString(c) + + // Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h + var d unsafe.Pointer + var l C.size_t + C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal) + for { + r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l) + if r == 0 { + break + } + + if r < 0 { + return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r)) + } + + msg := C.GoStringN((*C.char)(d), C.int(l)) + kv := strings.SplitN(msg, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("failed to parse field") + } + + entry.Fields[kv[0]] = kv[1] + } + + return entry, nil +} + +// SetDataThresold sets the data field size threshold for data returned by +// GetData. To retrieve the complete data fields this threshold should be +// turned off by setting it to 0, so that the library always returns the +// complete data objects. +func (j *Journal) SetDataThreshold(threshold uint64) error { + sd_journal_set_data_threshold, err := getFunction("sd_journal_set_data_threshold") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_set_data_threshold(sd_journal_set_data_threshold, j.cjournal, C.size_t(threshold)) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to set data threshold: %d", syscall.Errno(-r)) + } + + return nil +} + +// GetRealtimeUsec gets the realtime (wallclock) timestamp of the current +// journal entry. +func (j *Journal) GetRealtimeUsec() (uint64, error) { + var usec C.uint64_t + + sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &usec) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r)) + } + + return uint64(usec), nil +} + +// GetMonotonicUsec gets the monotonic timestamp of the current journal entry. +func (j *Journal) GetMonotonicUsec() (uint64, error) { + var usec C.uint64_t + var boot_id C.sd_id128_t + + sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r)) + } + + return uint64(usec), nil +} + +// GetCursor gets the cursor of the current journal entry. +func (j *Journal) GetCursor() (string, error) { + sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor") + if err != nil { + return "", err + } + + var d *C.char + // since the pointer is mutated by sd_journal_get_cursor, need to wait + // until after the call to free the memory + + j.mu.Lock() + r := C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &d) + j.mu.Unlock() + defer C.free(unsafe.Pointer(d)) + + if r < 0 { + return "", fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r)) + } + + cursor := C.GoString(d) + + return cursor, nil +} + +// TestCursor checks whether the current position in the journal matches the +// specified cursor +func (j *Journal) TestCursor(cursor string) error { + sd_journal_test_cursor, err := getFunction("sd_journal_test_cursor") + if err != nil { + return err + } + + c := C.CString(cursor) + defer C.free(unsafe.Pointer(c)) + + j.mu.Lock() + r := C.my_sd_journal_test_cursor(sd_journal_test_cursor, j.cjournal, c) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to test to cursor %q: %d", cursor, syscall.Errno(-r)) + } + + return nil +} + +// SeekHead seeks to the beginning of the journal, i.e. the oldest available +// entry. +func (j *Journal) SeekHead() error { + sd_journal_seek_head, err := getFunction("sd_journal_seek_head") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_head(sd_journal_seek_head, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to head of journal: %d", syscall.Errno(-r)) + } + + return nil +} + +// SeekTail may be used to seek to the end of the journal, i.e. the most recent +// available entry. +func (j *Journal) SeekTail() error { + sd_journal_seek_tail, err := getFunction("sd_journal_seek_tail") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_tail(sd_journal_seek_tail, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to tail of journal: %d", syscall.Errno(-r)) + } + + return nil +} + +// SeekRealtimeUsec seeks to the entry with the specified realtime (wallclock) +// timestamp, i.e. CLOCK_REALTIME. +func (j *Journal) SeekRealtimeUsec(usec uint64) error { + sd_journal_seek_realtime_usec, err := getFunction("sd_journal_seek_realtime_usec") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_realtime_usec(sd_journal_seek_realtime_usec, j.cjournal, C.uint64_t(usec)) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to %d: %d", usec, syscall.Errno(-r)) + } + + return nil +} + +// SeekCursor seeks to a concrete journal cursor. +func (j *Journal) SeekCursor(cursor string) error { + sd_journal_seek_cursor, err := getFunction("sd_journal_seek_cursor") + if err != nil { + return err + } + + c := C.CString(cursor) + defer C.free(unsafe.Pointer(c)) + + j.mu.Lock() + r := C.my_sd_journal_seek_cursor(sd_journal_seek_cursor, j.cjournal, c) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to cursor %q: %d", cursor, syscall.Errno(-r)) + } + + return nil +} + +// Wait will synchronously wait until the journal gets changed. The maximum time +// this call sleeps may be controlled with the timeout parameter. If +// sdjournal.IndefiniteWait is passed as the timeout parameter, Wait will +// wait indefinitely for a journal change. +func (j *Journal) Wait(timeout time.Duration) int { + var to uint64 + + sd_journal_wait, err := getFunction("sd_journal_wait") + if err != nil { + return -1 + } + + if timeout == IndefiniteWait { + // sd_journal_wait(3) calls for a (uint64_t) -1 to be passed to signify + // indefinite wait, but using a -1 overflows our C.uint64_t, so we use an + // equivalent hex value. + to = 0xffffffffffffffff + } else { + to = uint64(time.Now().Add(timeout).Unix() / 1000) + } + j.mu.Lock() + r := C.my_sd_journal_wait(sd_journal_wait, j.cjournal, C.uint64_t(to)) + j.mu.Unlock() + + return int(r) +} + +// GetUsage returns the journal disk space usage, in bytes. +func (j *Journal) GetUsage() (uint64, error) { + var out C.uint64_t + + sd_journal_get_usage, err := getFunction("sd_journal_get_usage") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_usage(sd_journal_get_usage, j.cjournal, &out) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get journal disk space usage: %d", syscall.Errno(-r)) + } + + return uint64(out), nil +} + +// GetUniqueValues returns all unique values for a given field. +func (j *Journal) GetUniqueValues(field string) ([]string, error) { + var result []string + + sd_journal_query_unique, err := getFunction("sd_journal_query_unique") + if err != nil { + return nil, err + } + + sd_journal_enumerate_unique, err := getFunction("sd_journal_enumerate_unique") + if err != nil { + return nil, err + } + + sd_journal_restart_unique, err := getFunction("sd_journal_restart_unique") + if err != nil { + return nil, err + } + + j.mu.Lock() + defer j.mu.Unlock() + + f := C.CString(field) + defer C.free(unsafe.Pointer(f)) + + r := C.my_sd_journal_query_unique(sd_journal_query_unique, j.cjournal, f) + + if r < 0 { + return nil, fmt.Errorf("failed to query journal: %d", syscall.Errno(-r)) + } + + // Implements the SD_JOURNAL_FOREACH_UNIQUE macro from sd-journal.h + var d unsafe.Pointer + var l C.size_t + C.my_sd_journal_restart_unique(sd_journal_restart_unique, j.cjournal) + for { + r = C.my_sd_journal_enumerate_unique(sd_journal_enumerate_unique, j.cjournal, &d, &l) + if r == 0 { + break + } + + if r < 0 { + return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r)) + } + + msg := C.GoStringN((*C.char)(d), C.int(l)) + kv := strings.SplitN(msg, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("failed to parse field") + } + + result = append(result, kv[1]) + } + + return result, nil +} diff --git a/vendor/github.com/coreos/go-systemd/sdjournal/read.go b/vendor/github.com/coreos/go-systemd/sdjournal/read.go new file mode 100644 index 000000000..b581f03b4 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/sdjournal/read.go @@ -0,0 +1,260 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdjournal + +import ( + "errors" + "fmt" + "io" + "log" + "strings" + "time" +) + +var ( + ErrExpired = errors.New("Timeout expired") +) + +// JournalReaderConfig represents options to drive the behavior of a JournalReader. +type JournalReaderConfig struct { + // The Since, NumFromTail and Cursor options are mutually exclusive and + // determine where the reading begins within the journal. The order in which + // options are written is exactly the order of precedence. + Since time.Duration // start relative to a Duration from now + NumFromTail uint64 // start relative to the tail + Cursor string // start relative to the cursor + + // Show only journal entries whose fields match the supplied values. If + // the array is empty, entries will not be filtered. + Matches []Match + + // If not empty, the journal instance will point to a journal residing + // in this directory. The supplied path may be relative or absolute. + Path string +} + +// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the +// systemd journal. A JournalReader is not safe for concurrent use by multiple goroutines. +type JournalReader struct { + journal *Journal + msgReader *strings.Reader +} + +// NewJournalReader creates a new JournalReader with configuration options that are similar to the +// systemd journalctl tool's iteration and filtering features. +func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) { + r := &JournalReader{} + + // Open the journal + var err error + if config.Path != "" { + r.journal, err = NewJournalFromDir(config.Path) + } else { + r.journal, err = NewJournal() + } + if err != nil { + return nil, err + } + + // Add any supplied matches + for _, m := range config.Matches { + r.journal.AddMatch(m.String()) + } + + // Set the start position based on options + if config.Since != 0 { + // Start based on a relative time + start := time.Now().Add(config.Since) + if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil { + return nil, err + } + } else if config.NumFromTail != 0 { + // Start based on a number of lines before the tail + if err := r.journal.SeekTail(); err != nil { + return nil, err + } + + // Move the read pointer into position near the tail. Go one further than + // the option so that the initial cursor advancement positions us at the + // correct starting point. + skip, err := r.journal.PreviousSkip(config.NumFromTail + 1) + if err != nil { + return nil, err + } + // If we skipped fewer lines than expected, we have reached journal start. + // Thus, we seek to head so that next invocation can read the first line. + if skip != config.NumFromTail+1 { + if err := r.journal.SeekHead(); err != nil { + return nil, err + } + } + } else if config.Cursor != "" { + // Start based on a custom cursor + if err := r.journal.SeekCursor(config.Cursor); err != nil { + return nil, err + } + } + + return r, nil +} + +// Read reads entries from the journal. Read follows the Reader interface so +// it must be able to read a specific amount of bytes. Journald on the other +// hand only allows us to read full entries of arbitrary size (without byte +// granularity). JournalReader is therefore internally buffering entries that +// don't fit in the read buffer. Callers should keep calling until 0 and/or an +// error is returned. +func (r *JournalReader) Read(b []byte) (int, error) { + var err error + + if r.msgReader == nil { + var c uint64 + + // Advance the journal cursor. It has to be called at least one time + // before reading + c, err = r.journal.Next() + + // An unexpected error + if err != nil { + return 0, err + } + + // EOF detection + if c == 0 { + return 0, io.EOF + } + + // Build a message + var msg string + msg, err = r.buildMessage() + + if err != nil { + return 0, err + } + r.msgReader = strings.NewReader(msg) + } + + // Copy and return the message + var sz int + sz, err = r.msgReader.Read(b) + if err == io.EOF { + // The current entry has been fully read. Don't propagate this + // EOF, so the next entry can be read at the next Read() + // iteration. + r.msgReader = nil + return sz, nil + } + if err != nil { + return sz, err + } + if r.msgReader.Len() == 0 { + r.msgReader = nil + } + + return sz, nil +} + +// Close closes the JournalReader's handle to the journal. +func (r *JournalReader) Close() error { + return r.journal.Close() +} + +// Rewind attempts to rewind the JournalReader to the first entry. +func (r *JournalReader) Rewind() error { + r.msgReader = nil + return r.journal.SeekHead() +} + +// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The +// follow will continue until a single time.Time is received on the until channel. +func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) (err error) { + + // Process journal entries and events. Entries are flushed until the tail or + // timeout is reached, and then we wait for new events or the timeout. + var msg = make([]byte, 64*1<<(10)) +process: + for { + c, err := r.Read(msg) + if err != nil && err != io.EOF { + break process + } + + select { + case <-until: + return ErrExpired + default: + if c > 0 { + if _, err = writer.Write(msg[:c]); err != nil { + break process + } + continue process + } + } + + // We're at the tail, so wait for new events or time out. + // Holds journal events to process. Tightly bounded for now unless there's a + // reason to unblock the journal watch routine more quickly. + events := make(chan int, 1) + pollDone := make(chan bool, 1) + go func() { + for { + select { + case <-pollDone: + return + default: + events <- r.journal.Wait(time.Duration(1) * time.Second) + } + } + }() + + select { + case <-until: + pollDone <- true + return ErrExpired + case e := <-events: + pollDone <- true + switch e { + case SD_JOURNAL_NOP, SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE: + // TODO: need to account for any of these? + default: + log.Printf("Received unknown event: %d\n", e) + } + continue process + } + } + + return +} + +// buildMessage returns a string representing the current journal entry in a simple format which +// includes the entry timestamp and MESSAGE field. +func (r *JournalReader) buildMessage() (string, error) { + var msg string + var usec uint64 + var err error + + if msg, err = r.journal.GetData("MESSAGE"); err != nil { + return "", err + } + + if usec, err = r.journal.GetRealtimeUsec(); err != nil { + return "", err + } + + timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond)) + + return fmt.Sprintf("%s %s\n", timestamp, msg), nil +} diff --git a/vendor/github.com/coreos/pkg/LICENSE b/vendor/github.com/coreos/pkg/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/vendor/github.com/coreos/pkg/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/coreos/pkg/NOTICE b/vendor/github.com/coreos/pkg/NOTICE new file mode 100644 index 000000000..b39ddfa5c --- /dev/null +++ b/vendor/github.com/coreos/pkg/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2014 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/pkg/README.md b/vendor/github.com/coreos/pkg/README.md new file mode 100644 index 000000000..ca68a07f0 --- /dev/null +++ b/vendor/github.com/coreos/pkg/README.md @@ -0,0 +1,4 @@ +a collection of go utility packages + +[![Build Status](https://travis-ci.org/coreos/pkg.png?branch=master)](https://travis-ci.org/coreos/pkg) +[![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/coreos/pkg) diff --git a/vendor/github.com/coreos/pkg/dlopen/dlopen.go b/vendor/github.com/coreos/pkg/dlopen/dlopen.go new file mode 100644 index 000000000..23774f612 --- /dev/null +++ b/vendor/github.com/coreos/pkg/dlopen/dlopen.go @@ -0,0 +1,82 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dlopen provides some convenience functions to dlopen a library and +// get its symbols. +package dlopen + +// #cgo LDFLAGS: -ldl +// #include <stdlib.h> +// #include <dlfcn.h> +import "C" +import ( + "errors" + "fmt" + "unsafe" +) + +var ErrSoNotFound = errors.New("unable to open a handle to the library") + +// LibHandle represents an open handle to a library (.so) +type LibHandle struct { + Handle unsafe.Pointer + Libname string +} + +// GetHandle tries to get a handle to a library (.so), attempting to access it +// by the names specified in libs and returning the first that is successfully +// opened. Callers are responsible for closing the handler. If no library can +// be successfully opened, an error is returned. +func GetHandle(libs []string) (*LibHandle, error) { + for _, name := range libs { + libname := C.CString(name) + defer C.free(unsafe.Pointer(libname)) + handle := C.dlopen(libname, C.RTLD_LAZY) + if handle != nil { + h := &LibHandle{ + Handle: handle, + Libname: name, + } + return h, nil + } + } + return nil, ErrSoNotFound +} + +// GetSymbolPointer takes a symbol name and returns a pointer to the symbol. +func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) { + sym := C.CString(symbol) + defer C.free(unsafe.Pointer(sym)) + + C.dlerror() + p := C.dlsym(l.Handle, sym) + e := C.dlerror() + if e != nil { + return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e))) + } + + return p, nil +} + +// Close closes a LibHandle. +func (l *LibHandle) Close() error { + C.dlerror() + C.dlclose(l.Handle) + e := C.dlerror() + if e != nil { + return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e))) + } + + return nil +} diff --git a/vendor/github.com/coreos/pkg/dlopen/dlopen_example.go b/vendor/github.com/coreos/pkg/dlopen/dlopen_example.go new file mode 100644 index 000000000..48a660104 --- /dev/null +++ b/vendor/github.com/coreos/pkg/dlopen/dlopen_example.go @@ -0,0 +1,56 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +build linux + +package dlopen + +// #include <string.h> +// #include <stdlib.h> +// +// int +// my_strlen(void *f, const char *s) +// { +// size_t (*strlen)(const char *); +// +// strlen = (size_t (*)(const char *))f; +// return strlen(s); +// } +import "C" + +import ( + "fmt" + "unsafe" +) + +func strlen(libs []string, s string) (int, error) { + h, err := GetHandle(libs) + if err != nil { + return -1, fmt.Errorf(`couldn't get a handle to the library: %v`, err) + } + defer h.Close() + + f := "strlen" + cs := C.CString(s) + defer C.free(unsafe.Pointer(cs)) + + strlen, err := h.GetSymbolPointer(f) + if err != nil { + return -1, fmt.Errorf(`couldn't get symbol %q: %v`, f, err) + } + + len := C.my_strlen(strlen, cs) + + return int(len), nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/go.mod b/vendor/github.com/fsouza/go-dockerclient/go.mod new file mode 100644 index 000000000..bcf549c21 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/go.mod @@ -0,0 +1,42 @@ +module github.com/fsouza/go-dockerclient + +require ( + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 + github.com/Microsoft/go-winio v0.4.11 + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 + github.com/containerd/continuity v0.0.0-20180814194400-c7c5070e6f6e // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.3.3 + github.com/docker/libnetwork v0.8.0-dev.2.0.20180608203834-19279f049241 // indirect + github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/gogo/protobuf v1.1.1 // indirect + github.com/golang/protobuf v1.2.0 // indirect + github.com/google/go-cmp v0.2.0 + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 + github.com/hpcloud/tail v1.0.0 // indirect + github.com/onsi/ginkgo v1.6.0 // indirect + github.com/onsi/gomega v1.4.1 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/opencontainers/runc v0.1.1 // indirect + github.com/pkg/errors v0.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.0.6 + github.com/stretchr/testify v1.2.2 // indirect + github.com/vishvananda/netlink v1.0.0 // indirect + github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect + golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect + golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87 + golang.org/x/text v0.3.0 // indirect + gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect + gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.2.1 // indirect + gotest.tools v2.1.0+incompatible // indirect +) diff --git a/vendor/github.com/hashicorp/errwrap/go.mod b/vendor/github.com/hashicorp/errwrap/go.mod new file mode 100644 index 000000000..c9b84022c --- /dev/null +++ b/vendor/github.com/hashicorp/errwrap/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/errwrap diff --git a/vendor/github.com/hashicorp/go-multierror/go.mod b/vendor/github.com/hashicorp/go-multierror/go.mod new file mode 100644 index 000000000..2534331d5 --- /dev/null +++ b/vendor/github.com/hashicorp/go-multierror/go.mod @@ -0,0 +1,3 @@ +module github.com/hashicorp/go-multierror + +require github.com/hashicorp/errwrap v1.0.0 diff --git a/vendor/github.com/stretchr/testify/go.mod b/vendor/github.com/stretchr/testify/go.mod new file mode 100644 index 000000000..90e5dbe25 --- /dev/null +++ b/vendor/github.com/stretchr/testify/go.mod @@ -0,0 +1,7 @@ +module github.com/stretchr/testify + +require ( + github.com/davecgh/go-spew v1.1.0 + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/objx v0.1.0 +) diff --git a/vendor/golang.org/x/text/go.mod b/vendor/golang.org/x/text/go.mod new file mode 100644 index 000000000..5eb1e8b16 --- /dev/null +++ b/vendor/golang.org/x/text/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/text + +require golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e diff --git a/vendor/gopkg.in/yaml.v2/go.mod b/vendor/gopkg.in/yaml.v2/go.mod new file mode 100644 index 000000000..1934e8769 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/go.mod @@ -0,0 +1,5 @@ +module "gopkg.in/yaml.v2" + +require ( + "gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405 +) |