diff options
90 files changed, 2042 insertions, 1410 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 848dc2b6d..c1b05c1bf 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -21,19 +21,17 @@ env: CIRRUS_SHELL: "/bin/bash" # Save a little typing (path relative to $CIRRUS_WORKING_DIR) SCRIPT_BASE: "./contrib/cirrus" - # Command to prefix every output line with a timestamp + CIRRUS_CLONE_DEPTH: 50 + # Command to prefix output lines with timing information # (can't do inline awk script, Cirrus-CI or YAML mangles quoting) TIMESTAMP: "awk --file ${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk" - # Command to log critical filesystems, types, and sizes. - DFCMD: "df -lhTx tmpfs" - CIRRUS_CLONE_DEPTH: 50 #### #### Cache-image names to test with ### - FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-4844850202017792" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-4844850202017792" - UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-4844850202017792" + FEDORA_CACHE_IMAGE_NAME: "fedora-30-libpod-5156500369047552" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-5156500369047552" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-5156500369047552" #### #### Variables for composing new cache-images (used in PR testing) from @@ -211,8 +209,9 @@ build_each_commit_task: setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' build_each_commit_script: + - 'source $SCRIPT_BASE/lib.sh' - 'git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH |& ${TIMESTAMP}' - - 'env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH |& ${TIMESTAMP}' + - 'make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH |& ${TIMESTAMP}' on_failure: failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' @@ -267,7 +266,7 @@ testing_task: matrix: # Images are generated separately, from build_images_task (below) image_name: "${FEDORA_CACHE_IMAGE_NAME}" - #image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" image_name: "${UBUNTU_CACHE_IMAGE_NAME}" timeout_in: 120m @@ -280,18 +279,16 @@ testing_task: setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' unit_test_script: '$SCRIPT_BASE/unit_test.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' - ginkgo_node_logs_script: 'cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log || echo "Ginkgo node logs not found"' - df_script: '${DFCMD}' - audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log' - journalctl_b_script: 'journalctl -b' + system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}' on_failure: failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' - # Job has already failed, don't fail again and miss collecting data - failed_ginkgo_node_logs_script: 'cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log || echo "Ginkgo node logs not found"' - failed_df_script: '${DFCMD}' - failed_audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log || echo "Uh oh, cat audit.log failed"' - failed_journalctl_b_script: 'journalctl -b || echo "Uh oh, journalctl -b failed"' + + always: &standardlogs + ginkgo_node_logs_script: '$SCRIPT_BASE/logcollector.sh ginkgo' + df_script: '$SCRIPT_BASE/logcollector.sh df' + audit_log_script: '$SCRIPT_BASE/logcollector.sh audit' + journal_script: '$SCRIPT_BASE/logcollector.sh journal' # This task executes tests under unique environments/conditions @@ -316,16 +313,14 @@ special_testing_rootless_task: setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' - df_script: '${DFCMD}' - audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log' - journalctl_b_script: 'journalctl -b' + system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}' on_failure: failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' - # Job has already failed, don't fail again and miss collecting data - failed_df_script: '${DFCMD}' - failed_audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log || echo "Uh oh, cat audit.log failed"' - failed_journalctl_b_script: 'journalctl -b || echo "Uh oh, journalctl -b failed"' + + always: + <<: *standardlogs + special_testing_in_podman_task: @@ -344,16 +339,12 @@ special_testing_in_podman_task: setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' - df_script: '${DFCMD}' - audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log' - journalctl_b_script: 'journalctl -b' on_failure: failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' - # Job has already failed, don't fail again and miss collecting data - failed_df_script: '${DFCMD}' - failed_audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log || echo "Uh oh, cat audit.log failed"' - failed_journalctl_b_script: 'journalctl -b || echo "Uh oh, journalctl -b failed"' + + always: + <<: *standardlogs # Test building of new cache-images for future PR testing, in this PR. @@ -383,6 +374,12 @@ test_build_cache_images_task: environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh |& ${TIMESTAMP}' + df_script: '${DFCMD}' + journalctl_b_script: 'journalctl -b' + + on_failure: + failed_df_script: '${DFCMD}' + failed_journalctl_b_script: 'journalctl -b || echo "Uh oh, journalctl -b failed"' # Test building of new cache-images for future PR testing, in this PR. @@ -401,8 +398,8 @@ verify_test_built_images_task: gce_instance: matrix: # Images are generated separately, from build_images_task (below) - #image_name: "fedora-28${BUILT_IMAGE_SUFFIX}" image_name: "fedora-29${BUILT_IMAGE_SUFFIX}" + image_name: "fedora-30${BUILT_IMAGE_SUFFIX}" image_name: "ubuntu-18${BUILT_IMAGE_SUFFIX}" env: @@ -416,66 +413,9 @@ verify_test_built_images_task: environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}' - ginkgo_node_logs_script: 'cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log || echo "Ginkgo node logs not found"' - df_script: '${DFCMD}' - audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log' - journalctl_b_script: 'journalctl -b' - on_failure: - # Job has already failed, don't fail again and miss collecting data - failed_ginkgo_node_logs_script: 'cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log || echo "Ginkgo node logs not found"' - failed_df_script: '${DFCMD}' - failed_audit_log_script: 'cat /var/log/audit/audit.log || cat /var/log/kern.log || echo "Uh oh, cat audit.log failed"' - failed_journalctl_b_script: 'journalctl -b || echo "Uh oh, journalctl -b failed"' - - -# Build new cache-images for future PR testing, but only after a PR merge. -# The cache-images save install/setup time needed test every PR. The 'active' images -# are selected by the 'image_name' items tasks above. Currently this requires -# manually updating the names, but this could be automated (see comment below). -build_cache_images_task: - # Only produce new cache-images after a PR merge, and if a magic string - # is present in the most recent ___commit-message___. - only_if: >- - $CIRRUS_BRANCH == 'master' && - $CIRRUS_CHANGE_MESSAGE =~ '.*\*\*\*\s*CIRRUS:\s*REBUILD\s*IMAGES\s*\*\*\*.*' - - # Require tests to pass first. - depends_on: - - "gating" - - "testing" - - "rootless_testing" - - # VMs created by packer are not cleaned up by cirrus - auto_cancellation: $CI != "true" - gce_instance: - image_project: "libpod-218412" - zone: "us-central1-a" # Required by Cirrus for the time being - cpu: 4 - memory: "4Gb" - disk: 200 - image_name: "${IMAGE_BUILDER_CACHE_IMAGE_NAME}" - # Additional permissions for building GCE images, within a GCE VM - scopes: - - compute - - devstorage.full_control - - environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' - build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh |& ${TIMESTAMP}' - - # TODO,Continuous Delivery: Automatically open a libpod PR after using 'sed' to replace - # the image_names with the new (just build) images. That will - # cause a new round of testing to happen (via the PR) using - # the new images. When all is good, the PR may be manually - # merged so all PR testing uses the new images. The script - # names (below) describe their purpose in this workflow. - # deploy_images_script: - # - clone_podman_release_branch.sh - # - modify_cirrus_yaml_image_names.sh - # - commit_and_create_upstream_pr.sh - - on_failure: - failed_master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh |& ${TIMESTAMP}' + always: + <<: *standardlogs # Post message to IRC if everything passed @@ -494,7 +434,6 @@ success_task: - "special_testing_in_podman" - "test_build_cache_images" - "verify_test_built_images" - - "build_cache_images" env: CIRRUS_WORKING_DIR: "/usr/src/libpod" @@ -286,7 +286,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method Attach(name: [string](https://godoc.org/builtin#string), detachKeys: [string](https://godoc.org/builtin#string), start: [bool](https://godoc.org/builtin#bool)) </div> -Attach takes the name or ID of a container and sets up a the ability to remotely attach to its console. The start +Attach takes the name or ID of a container and sets up the ability to remotely attach to its console. The start bool is whether you wish to start the container in question first. ### <a name="AttachControl"></a>func AttachControl <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -459,7 +459,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ExportContainer '{"name": method ExportImage(name: [string](https://godoc.org/builtin#string), destination: [string](https://godoc.org/builtin#string), compress: [bool](https://godoc.org/builtin#bool), tags: [[]string](#[]string)) [string](https://godoc.org/builtin#string)</div> ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also -a booleon option to force compression. It also takes in a string array of tags to be able to save multiple +a boolean option to force compression. It also takes in a string array of tags to be able to save multiple tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) error will be returned. See also [ImportImage](ImportImage). @@ -729,14 +729,14 @@ error will be returned if the container cannot be found. See also [InspectImage] <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method InspectImage(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> -InspectImage takes the name or ID of an image and returns a string respresentation of data associated with the +InspectImage takes the name or ID of an image and returns a string representation of data associated with the mage. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will be returned if the image cannot be found. ### <a name="InspectPod"></a>func InspectPod <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method InspectPod(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> -InspectPod takes the name or ID of an image and returns a string respresentation of data associated with the +InspectPod takes the name or ID of an image and returns a string representation of data associated with the pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. ### <a name="KillContainer"></a>func KillContainer @@ -1650,7 +1650,7 @@ name [string](https://godoc.org/builtin#string) star_count [int](https://godoc.org/builtin#int) ### <a name="InfoDistribution"></a>type InfoDistribution -InfoDistribution describes the the host's distribution +InfoDistribution describes the host's distribution distribution [string](https://godoc.org/builtin#string) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b7544ba0..b86f3e345 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -315,7 +315,7 @@ branches, which you may occasionally see [red bars on the status graph .](https://cirrus-ci.com/github/containers/libpod/master) When the graph shows mostly green bars on the right, it's a good indication -the master branch is currently stable. Alternating red/green bars is indicitave +the master branch is currently stable. Alternating red/green bars is indicative of a testing "flake", and should be examined (anybody can do this): * *One or a small handful of tests, on a single task, (i.e. specific distro/version) diff --git a/Dockerfile.fedora b/Dockerfile.fedora index c34d4bb16..5cbc6e1fe 100644 --- a/Dockerfile.fedora +++ b/Dockerfile.fedora @@ -1,9 +1,8 @@ -FROM registry.fedoraproject.org/fedora:29 +FROM registry.fedoraproject.org/fedora:30 RUN dnf -y install btrfs-progs-devel \ atomic-registries \ bzip2 \ - conmon \ device-mapper-devel \ findutils \ git \ @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= -EPOCH_TEST_COMMIT ?= 90e3c9002b2293569e0cec168a30ecb962b00034 +EPOCH_TEST_COMMIT ?= 5df8178c995fe6422f0fed258cd65f6cbcf26f2e HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -220,8 +220,11 @@ localintegration: varlink_generate test-binaries ginkgo remoteintegration: varlink_generate test-binaries ginkgo-remote -localsystem: .install.ginkgo - ginkgo -v -noColor test/system/ +localsystem: + if timeout -v 1 true; then PODMAN=./bin/podman bats test/system/; else echo "Skipping localsystem: 'timeout -v' unavailable'"; fi + +remotesystem: + @echo "remotesystem - unimplemented" system.test-binary: .install.ginkgo $(GO) test -c ./test/system @@ -29,6 +29,7 @@ This project tests all builds against each supported version of Fedora, the late 1. Integrate libpod into CRI-O to replace its existing container management backend 1. Further work on the podman pod command 1. Further improvements on rootless containers +1. Support for CGroups V2 (and resource isolation for rootless containers) ## Rootless Podman can be easily run as a normal user, without requiring a setuid binary. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d5717f7db..2264dbaa9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,14 @@ # Release Notes +## 1.4.2 +### Bugfixes +- Fixed a bug where Podman could not run containers using an older version of Systemd as init ([#3295](https://github.com/containers/libpod/issues/3295)) + +### Misc +- Updated vendored Buildah to v1.9.0 to resolve a critical bug with Dockerfile `RUN` instructions +- The error message for running `podman kill` on containers that are not running has been improved +- The Podman remote client can now log to a file if syslog is not available + ## 1.4.1 ### Features - The `podman exec` command now sets its error code differently based on whether the container does not exist, and the command in the container does not exist diff --git a/changelog.txt b/changelog.txt index 3b1260c90..9e201ecb4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,32 @@ +- Changelog for v1.4.2 (2019-06-18) + * Update release notes for Podman 1.4.2 + * updating podman logo files + * fix port -l timing with healthchecks + * Bump Buildah to v1.9.0 + * Swap to using the on-disk spec for inspect mounts + * Replace podman.svg; closes #3350 + * cmd, docs, test: fix some typos + * run BATS tests in Cirrus + * Move the Config portion of Inspect into libpod + * Add remote client logging to a file + * Fix subgidname option in docs for podman run + * stop/kill: inproper state errors: s/in state/is in state/ + * test: add test for logs -f + * kill: print ID and state for non-running containers + * API.md: fix few typos + * docs/podamn.1.md: fix typo: remove double the + * CONTRIBUTING.md: fix typo + * Remove unnecessary var type to fix lint warning + * Move installPrefix and etcDir into runtime.go + * Improve DESTDIR/PREFIX/ETCDIR handling + * Bump gitvalidation epoch + * Bump to v1.4.2-dev + * Change container command to contained + * Cirrus: Simplify log collection commands + * Accidently removed /run/lock from systemd mounts + * Add warning while untagging an image podman-load + * podman copy files to the volume with a container + - Changelog for v1.4.1 (2019-06-14) * Completely disable global options test * Update release notes for 1.4.1 diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 01e2ec701..db0b8241e 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -26,9 +26,9 @@ var ( commitCommand.Remote = remoteclient return commitCmd(&commitCommand) }, - Example: `podman commit -q --message "committing container to image" reverent_golick image-commited - podman commit -q --author "firstName lastName" reverent_golick image-commited - podman commit -q --pause=false containerID image-commited`, + Example: `podman commit -q --message "committing container to image" reverent_golick image-committed + podman commit -q --author "firstName lastName" reverent_golick image-committed + podman commit -q --pause=false containerID image-committed`, } ) diff --git a/cmd/podman/export.go b/cmd/podman/export.go index 82a4c13e7..f2336167b 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -7,8 +7,8 @@ import ( "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) var ( @@ -62,7 +62,7 @@ func exportCmd(c *cliconfig.ExportValues) error { if len(output) == 0 { file := os.Stdout - if logrus.IsTerminal(file) { + if terminal.IsTerminal(int(file.Fd())) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } output = "/dev/stdout" diff --git a/cmd/podman/main.go b/cmd/podman/main.go index cbca32cc8..847cc0731 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -104,9 +104,7 @@ func before(cmd *cobra.Command, args []string) error { logrus.Errorf(err.Error()) os.Exit(1) } - if err := setSyslog(); err != nil { - return err - } + if err := setupRootless(cmd, args); err != nil { return err } @@ -121,6 +119,9 @@ func before(cmd *cobra.Command, args []string) error { return err } logrus.SetLevel(level) + if err := setSyslog(); err != nil { + return err + } if err := setRLimits(); err != nil { return err diff --git a/cmd/podman/main_remote.go b/cmd/podman/main_remote.go index 1b9430e92..ecbb44d5a 100644 --- a/cmd/podman/main_remote.go +++ b/cmd/podman/main_remote.go @@ -3,8 +3,13 @@ package main import ( + "fmt" + "os" "os/user" + "path/filepath" + "github.com/docker/docker/pkg/homedir" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -20,11 +25,41 @@ func init() { rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteUserName, "username", username, "username on the remote host") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteHost, "remote-host", "", "remote host") // TODO maybe we allow the altering of this for bridge connections? - //rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.VarlinkAddress, "varlink-address", adapter.DefaultAddress, "address of the varlink socket") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic") + // rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.VarlinkAddress, "varlink-address", adapter.DefaultAddress, "address of the varlink socket") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic. Logged to ~/.config/containers/podman.log") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") } func setSyslog() error { + // Log to file if not using syslog + homeDir := homedir.Get() + path := filepath.Join(homeDir, ".config", "containers") + + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := os.MkdirAll(path, 0750); err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + return err + } + } + + // Update path to include file name + path = filepath.Join(path, "podman.log") + + // Create the log file if doesn't exist. And append to it if it already exists. + file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0640) + if err != nil { + // Cannot open log file. Logging to stderr + fmt.Fprintf(os.Stderr, "%v", err) + return err + } else { + formatter := new(logrus.TextFormatter) + formatter.FullTimestamp = true + logrus.SetFormatter(formatter) + logrus.SetOutput(file) + } + + // Note this message is only logged if --log-level >= Info! + logrus.Infof("Logging level set to %s", logrus.GetLevel().String()) return nil } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index 4d204337e..e59c9df5f 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -9,8 +9,8 @@ import ( "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) const ( @@ -82,7 +82,7 @@ func saveCmd(c *cliconfig.SaveValues) error { if len(c.Output) == 0 { fi := os.Stdout - if logrus.IsTerminal(fi) { + if terminal.IsTerminal(int(fi.Fd())) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } c.Output = "/dev/stdout" diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index c97eaa290..f24a2358f 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -710,7 +710,7 @@ func portsToString(ports []ocicni.PortMapping) string { } // GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the -// contruction of the runlabel output and environment variables +// construction of the runlabel output and environment variables func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { var ( newImage *image.Image diff --git a/cmd/podman/shared/container_inspect.go b/cmd/podman/shared/container_inspect.go index 97a1d0238..c89daf6bb 100644 --- a/cmd/podman/shared/container_inspect.go +++ b/cmd/podman/shared/container_inspect.go @@ -1,9 +1,6 @@ package shared import ( - "strings" - - "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" cc "github.com/containers/libpod/pkg/spec" "github.com/docker/go-connections/nat" @@ -17,7 +14,6 @@ import ( type InspectContainer struct { *libpod.InspectContainerData HostConfig *InspectContainerHostConfig `json:"HostConfig"` - Config *InspectContainerConfig `json:"Config"` } // InspectContainerHostConfig holds Container configuration that is not specific @@ -82,33 +78,8 @@ type InspectContainerHostConfig struct { Tmpfs []string `json:"Tmpfs"` } -// InspectContainerConfig holds further data about a container, again mostly -// not directly stored in Libpod. This struct is matched to the output of -// `docker inspect`. -type InspectContainerConfig struct { - Hostname string `json:"Hostname"` - DomainName string `json:"Domainname"` //TODO - User specs.User `json:"User"` - AttachStdin bool `json:"AttachStdin"` //TODO - AttachStdout bool `json:"AttachStdout"` //TODO - AttachStderr bool `json:"AttachStderr"` //TODO - Tty bool `json:"Tty"` - OpenStdin bool `json:"OpenStdin"` - StdinOnce bool `json:"StdinOnce"` //TODO - Env []string `json:"Env"` - Cmd []string `json:"Cmd"` - Image string `json:"Image"` - Volumes map[string]struct{} `json:"Volumes"` - WorkingDir string `json:"WorkingDir"` - Entrypoint string `json:"Entrypoint"` - Labels map[string]string `json:"Labels"` - Annotations map[string]string `json:"Annotations"` - StopSignal uint `json:"StopSignal"` - Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` -} - // InspectLogConfig holds information about a container's configured log driver -// and is presently unused. It is retained for Docker compatability. +// and is presently unused. It is retained for Docker compatibility. type InspectLogConfig struct { Type string `json:"Type"` Config map[string]string `json:"Config"` //idk type, TODO @@ -181,21 +152,6 @@ func GetCtrInspectInfo(config *libpod.ContainerConfig, ctrInspectData *libpod.In SecurityOpt: createArtifact.SecurityOpts, Tmpfs: createArtifact.Tmpfs, }, - &InspectContainerConfig{ - Hostname: spec.Hostname, - User: spec.Process.User, - Env: spec.Process.Env, - Image: config.RootfsImageName, - WorkingDir: spec.Process.Cwd, - Labels: config.Labels, - Annotations: spec.Annotations, - Tty: spec.Process.Terminal, - OpenStdin: config.Stdin, - StopSignal: config.StopSignal, - Cmd: config.Spec.Process.Args, - Entrypoint: strings.Join(createArtifact.Entrypoint, " "), - Healthcheck: config.HealthCheckConfig, - }, } return data, nil } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 7cf230605..eee5f515d 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -811,7 +811,7 @@ func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod) } if startPeriodDuration < time.Duration(0) { - return nil, errors.New("healthcheck-start-period must be a 0 seconds or greater") + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") } hc.StartPeriod = startPeriodDuration diff --git a/cmd/podman/system_df.go b/cmd/podman/system_df.go index 840916547..d2163d0d7 100644 --- a/cmd/podman/system_df.go +++ b/cmd/podman/system_df.go @@ -586,7 +586,7 @@ func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error { } volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) if err != nil { - return errors.Wrapf(err, "error getting verbose ouput of volumes") + return errors.Wrapf(err, "error getting verbose output of volumes") } os.Stderr.WriteString("\nLocal Volumes space usage:\n\n") out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: volumeVerboseFormat, Fields: volumeVerboseHeader} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 5b3d5ae4c..9410b9459 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -207,7 +207,7 @@ type ContainerNameSpace ( ipc: string ) -# InfoDistribution describes the the host's distribution +# InfoDistribution describes the host's distribution type InfoDistribution ( distribution: string, version: string @@ -671,7 +671,7 @@ method PauseContainer(name: string) -> (container: string) # See also [PauseContainer](#PauseContainer). method UnpauseContainer(name: string) -> (container: string) -# Attach takes the name or ID of a container and sets up a the ability to remotely attach to its console. The start +# Attach takes the name or ID of a container and sets up the ability to remotely attach to its console. The start # bool is whether you wish to start the container in question first. method Attach(name: string, detachKeys: string, start: bool) -> () @@ -744,7 +744,7 @@ method BuildImage(build: BuildInfo) -> (image: MoreResponse) # This function is not implemented yet. # method CreateImage() -> (notimplemented: NotImplemented) -# InspectImage takes the name or ID of an image and returns a string respresentation of data associated with the +# InspectImage takes the name or ID of an image and returns a string representation of data associated with the #image. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will # be returned if the image cannot be found. method InspectImage(name: string) -> (image: string) @@ -810,7 +810,7 @@ method Commit(name: string, image_name: string, changes: []string, author: strin method ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) -> (image: string) # ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also -# a booleon option to force compression. It also takes in a string array of tags to be able to save multiple +# a boolean option to force compression. It also takes in a string array of tags to be able to save multiple # tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID # of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) # error will be returned. See also [ImportImage](ImportImage). @@ -915,7 +915,7 @@ method ListPods() -> (pods: []ListPodData) # ~~~ method GetPod(name: string) -> (pod: ListPodData) -# InspectPod takes the name or ID of an image and returns a string respresentation of data associated with the +# InspectPod takes the name or ID of an image and returns a string representation of data associated with the # pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will # be returned if the pod cannot be found. method InspectPod(name: string) -> (pod: string) diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md index 94494a558..5ff4f290f 100644 --- a/contrib/cirrus/README.md +++ b/contrib/cirrus/README.md @@ -96,10 +96,18 @@ images following the standard naming format; ***however, only runs a limited sub-set of automated tests***. Validating newly built images fully, requires updating ``.cirrus.yml``. -***Manual Steps:*** Assuming `verify_test_built_images` passes, then +***N/B: Steps below are performed by automation*** + +1. Using the just build VM images, launch VMs and wait for them to boot. + +2. Execute the `setup_environment.sh` as in the `testing` task. + +2. Execute the `integration_test.sh` as in the `testing` task. + + +***Manual Steps:*** Assuming the automated steps pass, then you'll find the new image names displayed at the end of the -`test_build_cache_images_task` in the `build_vm_images` output. -For example: +`test_build_cache_images`. For example: ``` @@ -135,18 +143,6 @@ the magic ``***CIRRUS: TEST IMAGES***`` string. Keeping it and and test images again. -### ``build_cache_images`` Task *(Deprecated)* - -Exactly the same as ``test_build_cache_images_task`` task, but only runs on -the master branch. Requires a magic string to be in the `HEAD` -commit message: ``***CIRRUS: BUILD IMAGES***`` - -When successful, the manifest file along with all VM disks, are moved -into a dedicated google storage bucket, separate from the one used by -`test_build_cache_images_task`. These may be used to create new cache-images for -PR testing by manually importing them as described above. - - ### Base-images Base-images are VM disk-images specially prepared for executing as GCE VMs. @@ -158,10 +154,9 @@ as the standard 'cloud-init' services. with services pre-installed, for many platforms. For example, RHEL, CentOS, and Ubuntu. -* Google does ***not*** provide any images for Fedora or Fedora Atomic - Host (as of 11/2018), nor do they provide a base-image prepared to - run packer for creating other images in the ``build_vm_images`` Task - (above). +* Google does ***not*** provide any images for Fedora (as of 5/2019), nor do + they provide a base-image prepared to run packer for creating other images + in the ``test_build_vm_images`` Task (above). * Base images do not need to be produced often, but doing so completely manually would be time-consuming and error-prone. Therefor a special diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index f9ba010cd..b163834d5 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -28,8 +28,6 @@ then -e "DIST=$OS_RELEASE_ID" \ -e "CONTAINER_RUNTIME=$CONTAINER_RUNTIME" \ $IN_PODMAN_IMAGE bash $GOSRC/$SCRIPT_BASE/container_test.sh -b -i -t - - exit $? elif [[ "$SPECIALMODE" == "rootless" ]] then req_env_var ROOTLESS_USER @@ -52,5 +50,4 @@ else else make local${TESTSUITE} fi - exit $? fi diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 97901cfc7..30141db67 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -55,15 +55,15 @@ PACKER_VER="1.3.5" # CSV of cache-image names to build (see $PACKER_BASE/libpod_images.json) # Base-images rarely change, define them here so they're out of the way. -PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,fedora-29,fedora-28}" +PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,fedora-30,fedora-29}" # Google-maintained base-image names UBUNTU_BASE_IMAGE="ubuntu-1804-bionic-v20181203a" # Manually produced base-image names (see $SCRIPT_BASE/README.md) -FEDORA_BASE_IMAGE="fedora-cloud-base-29-1-2-1541789245" -# FEDORA_BASE_IMAGE: "fedora-cloud-base-30-1-2-1556821664" -PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-28-1-1-1544474897" -# PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-29-1-2-1541789245" +FEDORA_BASE_IMAGE="fedora-cloud-base-30-1-2-1559164849" +PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-29-1-2-1559164849" BUILT_IMAGE_SUFFIX="${BUILT_IMAGE_SUFFIX:--$CIRRUS_REPO_NAME-${CIRRUS_BUILD_ID}}" +# IN_PODMAN container image +IN_PODMAN_IMAGE="quay.io/libpod/in_podman:latest" # Safe env. vars. to transfer from root -> $ROOTLESS_USER (go env handled separetly) ROOTLESS_ENV_RE='(CIRRUS_.+)|(ROOTLESS_.+)|(.+_IMAGE.*)|(.+_BASE)|(.*DIRPATH)|(.*FILEPATH)|(SOURCE.*)|(DEPEND.*)|(.+_DEPS_.+)|(OS_REL.*)|(.+_ENV_RE)|(TRAVIS)|(CI.+)|(TEST_REMOTE.*)' @@ -74,9 +74,6 @@ SPECIALMODE="${SPECIALMODE:-none}" TEST_REMOTE_CLIENT="${TEST_REMOTE_CLIENT:-false}" export CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-podman} -# IN_PODMAN container image -IN_PODMAN_IMAGE="quay.io/libpod/in_podman:latest" - # When running as root, this may be empty or not, as a user, it MUST be set. if [[ "$USER" == "root" ]] then @@ -211,8 +208,25 @@ setup_rootless() { # Works with older versions of bash printf "${_env_var_name}=%q\n" "$(printenv $_env_var_name)" >> "/home/$ROOTLESS_USER/.bashrc" done - echo "Ensure the systems ssh process is up and running" - systemctl --wait restart sshd # a regular 'start' could hang forever + + echo "Ensure the systems ssh process is up and running within 5 minutes" + systemctl start sshd + NOW=$(date +%s) + TIMEOUT=$(date --date '+5 minutes' +%s) + while [[ "$(date +%s)" -lt "$TIMEOUT" ]] + do + if timeout --foreground -k 1s 1s \ + ssh $ROOTLESS_USER@localhost \ + -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no \ + true + then + break + else + sleep 2s + fi + done + [[ "$(date +%s)" -lt "$TIMEOUT" ]] || \ + die 11 "Timeout exceeded waiting for localhost ssh capability" } # Helper/wrapper script to only show stderr/stdout on non-zero exit @@ -347,7 +361,7 @@ _finalize(){ set +e # make errors non-fatal echo "Removing leftover giblets from cloud-init" cd / - sudo rm -rf /var/lib/cloud/instance? + sudo rm -rf /var/lib/cloud/instanc* sudo rm -rf /root/.ssh/* sudo rm -rf /home/* sudo rm -rf /tmp/* diff --git a/contrib/cirrus/logcollector.sh b/contrib/cirrus/logcollector.sh new file mode 100755 index 000000000..425a619b0 --- /dev/null +++ b/contrib/cirrus/logcollector.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +source $(dirname $0)/lib.sh + +req_env_var CIRRUS_WORKING_DIR OS_RELEASE_ID + +# Assume there are other log collection commands to follow - Don't +# let one break another that may be useful, but also keep any +# actual script-problems fatal so they are noticed right away. +showrun() { + echo '+ '$(printf " %q" "$@") + set +e + echo '------------------------------------------------------------' + "$@" + local status=$? + [[ $status -eq 0 ]] || \ + echo "[ rc = $status -- proceeding anyway ]" + echo '------------------------------------------------------------' + set -e +} + +case $1 in + audit) + case $OS_RELEASE_ID in + ubuntu) showrun cat /var/log/kern.log ;; + fedora) showrun cat /var/log/audit/audit.log ;; + *) bad_os_id_ver ;; + esac + ;; + df) showrun df -lhTx tmpfs ;; + ginkgo) showrun cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log ;; + journal) showrun journalctl -b ;; + *) die 1 "Warning, $(basename $0) doesn't know how to handle the parameter '$1'" +esac diff --git a/contrib/cirrus/packer/cloud-init/fedora/cloud-init.service b/contrib/cirrus/packer/cloud-init/fedora/cloud-init.service new file mode 100644 index 000000000..4d2197d87 --- /dev/null +++ b/contrib/cirrus/packer/cloud-init/fedora/cloud-init.service @@ -0,0 +1,20 @@ +[Unit] +Description=Initial cloud-init job (metadata service crawler) +DefaultDependencies=no +Wants=cloud-init-local.service +After=cloud-init-local.service +Wants=google-network-daemon.service +After=google-network-daemon.service +Before=systemd-user-sessions.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/cloud-init init +RemainAfterExit=yes +TimeoutSec=0 + +# Output needs to appear in instance console output +StandardOutput=journal+console + +[Install] +WantedBy=cloud-init.target diff --git a/contrib/cirrus/packer/fedora_base-setup.sh b/contrib/cirrus/packer/fedora_base-setup.sh index 2e6d3eceb..a425b2b57 100644 --- a/contrib/cirrus/packer/fedora_base-setup.sh +++ b/contrib/cirrus/packer/fedora_base-setup.sh @@ -16,11 +16,17 @@ echo "Updating packages" ooe.sh dnf -y update echo "Installing necessary packages and google services" -ooe.sh dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin +ooe.sh dnf -y install rng-tools google-compute-engine-tools google-compute-engine-oslogin ethtool echo "Enabling services" ooe.sh systemctl enable rngd +# There is a race that can happen on boot between the GCE services configuring +# the VM, and cloud-init trying to do similar activities. Use a customized +# unit file to make sure cloud-init starts after the google-compute-* services. +echo "Setting cloud-init service to start after google-network-daemon.service" +cp -v $GOSRC/$PACKER_BASE/cloud-init/fedora/cloud-init.service /etc/systemd/system/ + rh_finalize echo "SUCCESS!" diff --git a/contrib/cirrus/packer/libpod_base_images.yml b/contrib/cirrus/packer/libpod_base_images.yml index 560cb321c..e519d2fba 100644 --- a/contrib/cirrus/packer/libpod_base_images.yml +++ b/contrib/cirrus/packer/libpod_base_images.yml @@ -78,7 +78,7 @@ builders: ssh_username: 'root' - <<: *nested_virt - name: 'prior-fedora' + name: 'prior_fedora' iso_url: '{{user `PRIOR_FEDORA_IMAGE_URL`}}' iso_checksum_url: '{{user `PRIOR_FEDORA_CSUM_URL`}}' @@ -121,7 +121,7 @@ provisioners: post-processors: - - type: "compress" - only: ['fedora', 'prior-fedora'] + only: ['fedora', 'prior_fedora'] output: '/tmp/{{build_name}}/disk.raw.tar.gz' format: '.tar.gz' compression_level: 9 @@ -136,7 +136,7 @@ post-processors: image_description: 'Based on {{user `FEDORA_IMAGE_URL`}}' image_family: '{{user `FEDORA_BASE_IMAGE_NAME`}}' - <<: *gcp_import - only: ['prior-fedora'] + only: ['prior_fedora'] image_name: "{{user `PRIOR_FEDORA_BASE_IMAGE_NAME`}}-{{user `TIMESTAMP`}}" image_description: 'Based on {{user `PRIOR_FEDORA_IMAGE_URL`}}' image_family: '{{user `PRIOR_FEDORA_BASE_IMAGE_NAME`}}' diff --git a/contrib/cirrus/packer/libpod_images.yml b/contrib/cirrus/packer/libpod_images.yml index 34d4db7fb..c25da25ac 100644 --- a/contrib/cirrus/packer/libpod_images.yml +++ b/contrib/cirrus/packer/libpod_images.yml @@ -55,11 +55,11 @@ builders: # v----- is a YAML alias, allows partial re-use of the anchor object - <<: *gce_hosted_image - name: 'fedora-29' + name: 'fedora-30' source_image: '{{user `FEDORA_BASE_IMAGE`}}' - <<: *gce_hosted_image - name: 'fedora-28' + name: 'fedora-29' source_image: '{{user `PRIOR_FEDORA_BASE_IMAGE`}}' # The brains of the operation, making actual modifications to the base-image. diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 13bce506a..8fdcf5897 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -33,20 +33,8 @@ done # (see docs). case "${OS_REL_VER}" in ubuntu-18) ;; - fedora-29) - # Occasionally, and seemingly only on F29 the root disk fails to expand - # upon boot. When this happens, any number of failures could occur if - # space runs out. Until there is time to investigate the actual cause, - # workaround this problem by detecting it and acting accordingly. - REMAINING=$(df /dev/sda1 | tail -1 | awk '{print $4}') - if [[ "$REMAINING" -lt "100000000" ]] # .cirrus.yml specifies 200gig - then - echo "Fixing failure to expand root filesystem" - growpart /dev/sda 1 # device guaranteed by cloud provider - resize2fs /dev/sda1 # growpart & resuze guaranteed by base-image - fi - ;; - fedora-28) ;; + fedora-30) ;; + fedora-29) ;; centos-7) # Current VM is an image-builder-image no local podman/testing echo "No further setup required for VM image building" exit 0 diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh index a2cc1af05..cbc481d6b 100755..120000 --- a/contrib/cirrus/system_test.sh +++ b/contrib/cirrus/system_test.sh @@ -1,21 +1 @@ -#!/bin/bash - -set -e -source $(dirname $0)/lib.sh - -req_env_var GOSRC OS_RELEASE_ID OS_RELEASE_VER - -set -x -cd "$GOSRC" - -case "${OS_RELEASE_ID}" in - ubuntu) ;& # Continue to the next item - fedora) - make install.tools - make - make test-binaries - ;; - *) bad_os_id_ver ;; -esac - -make localsystem +integration_test.sh
\ No newline at end of file diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index 630371c76..c886fc9aa 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -1,18 +1,15 @@ -FROM fedora:29 +FROM fedora:30 RUN dnf -y install \ atomic-registries \ btrfs-progs-devel \ bzip2 \ - conmon \ container-selinux \ containernetworking-cni \ - containernetworking-cni-devel \ device-mapper-devel \ findutils \ git \ glib2-devel \ glibc-static \ - gnupg \ golang \ gpgme-devel \ iptables \ diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index d0ad07044..68d02325a 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 1.4.2 +Version: 1.4.3 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md index 12e8c3bf0..120573235 100644 --- a/docs/podman-build.1.md +++ b/docs/podman-build.1.md @@ -172,7 +172,7 @@ value can be entered. The password is entered without echo. This is a Docker specific option to disable image verification to a Docker registry and is not supported by Podman. This flag is a NOOP and provided -soley for scripting compatibility. +solely for scripting compatibility. **--dns**=*dns* diff --git a/docs/podman-commit.1.md b/docs/podman-commit.1.md index bf0df0dda..a269d0fae 100644 --- a/docs/podman-commit.1.md +++ b/docs/podman-commit.1.md @@ -58,7 +58,7 @@ Suppress output ## EXAMPLES ``` -$ podman commit --change CMD=/bin/bash --change ENTRYPOINT=/bin/sh --change LABEL=blue=image reverent_golick image-commited +$ podman commit --change CMD=/bin/bash --change ENTRYPOINT=/bin/sh --change LABEL=blue=image reverent_golick image-committed Getting image source signatures Copying blob sha256:b41deda5a2feb1f03a5c1bb38c598cbc12c9ccd675f438edc6acd815f7585b86 25.80 MB / 25.80 MB [======================================================] 0s @@ -70,17 +70,17 @@ e3ce4d93051ceea088d1c242624d659be32cf1667ef62f1d16d6b60193e2c7a8 ``` ``` -$ podman commit -q --message "committing container to image" reverent_golick image-commited +$ podman commit -q --message "committing container to image" reverent_golick image-committed e3ce4d93051ceea088d1c242624d659be32cf1667ef62f1d16d6b60193e2c7a8 ``` ``` -$ podman commit -q --author "firstName lastName" reverent_golick image-commited +$ podman commit -q --author "firstName lastName" reverent_golick image-committed e3ce4d93051ceea088d1c242624d659be32cf1667ef62f1d16d6b60193e2c7a8 ``` ``` -$ podman commit -q --pause=false containerID image-commited +$ podman commit -q --pause=false containerID image-committed e3ce4d93051ceea088d1c242624d659be32cf1667ef62f1d16d6b60193e2c7a8 ``` diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index a4eebef4c..b08488d7b 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -55,11 +55,11 @@ Block IO weight (relative weight) accepts a weight value between 10 and 1000. Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`). -**--cap-add**=*capabilitiy* +**--cap-add**=*capability* Add Linux capabilities -**--cap-drop**=*capabilitiy* +**--cap-drop**=*capability* Drop Linux capabilities @@ -382,7 +382,7 @@ Not implemented **--log-driver**="*k8s-file*" -Logging driver for the container. Currently not supported. This flag is a NOOP provided soley for scripting compatibility. +Logging driver for the container. Currently available options are *k8s-file* and *journald*, with *json-file* aliased to *k8s-file* for scripting compatibility. **--log-opt**=*path* diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index 88d8e9627..76baad83a 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -20,7 +20,7 @@ Output to the given file, instead of STDOUT. If the file already exists, `genera **--service**, **-s** -Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod ouput. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A +Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod output. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A random port is assigned by Podman in the specification. ## Examples diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 123f7ee5f..042a7a561 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -395,7 +395,7 @@ Not implemented **--log-driver**="*k8s-file*" -Logging driver for the container. Currently not supported. This flag is a NOOP provided soley for scripting compatibility. +Logging driver for the container. Currently available options are *k8s-file* and *journald*, with *json-file* aliased to *k8s-file* for scripting compatibility. **--log-opt**=*path* diff --git a/docs/podman-system-migrate.1.md b/docs/podman-system-migrate.1.md index 48f3bca8f..1efa779ce 100644 --- a/docs/podman-system-migrate.1.md +++ b/docs/podman-system-migrate.1.md @@ -11,11 +11,24 @@ podman\-system\-migrate - Migrate container to the latest version of podman **podman system migrate** takes care of migrating existing containers to the latest version of podman if any change is necessary. +"Rootless Podman uses a pause process to keep the unprivileged +namespaces alive. This prevents any change to the `/etc/subuid` and +`/etc/subgid` files from being propagated to the rootless containers +while the pause process is running. + +For these changes to be propagated, it is necessary to first stop all +running containers associated with the user and to also stop the pause +process and delete its pid file. Instead of doing it manually, `podman +system migrate` can be used to stop both the running containers and the +pause process. The `/etc/subuid` and `/etc/subgid` files can then be +edited or changed with usermod to recreate the user namespace with the +newly configured mappings. + ## SYNOPSIS **podman system migrate** ## SEE ALSO -`podman(1)`, `libpod.conf(5)` +`podman(1)`, `libpod.conf(5)`, `usermod(8)` ## HISTORY April 2019, Originally compiled by Giuseppe Scrivano (gscrivan at redhat dot com) diff --git a/docs/podman.1.md b/docs/podman.1.md index 84e695d23..c23075718 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -90,6 +90,8 @@ Storage driver option, Default storage driver options are configured in /etc/con output logging information to syslog as well as the console +On remote clients, logging is directed to the file ~/.config/containers/podman.log + **--version**, **-v** Print the version @@ -106,13 +108,13 @@ the exit codes follow the `chroot` standard, see below: Error: unknown flag: --foo 125 -**_126_** if executing a **_contained command_** and the the **_command_** cannot be invoked +**_126_** if executing a **_contained command_** and the **_command_** cannot be invoked $ podman run busybox /etc; echo $? Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error 126 -**_127_** if executing a **_contained command_** and the the **_command_** cannot be found +**_127_** if executing a **_contained command_** and the **_command_** cannot be found $ podman run busybox foo; echo $? Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error 127 diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh index 12dd211f4..90e3aea8e 100755 --- a/hack/get_ci_vm.sh +++ b/hack/get_ci_vm.sh @@ -168,7 +168,7 @@ parse_args(){ if echo "$IMAGE_NAME" | grep -q "image-builder-image" then echo -e "Creating an image-builder VM, I hope you know what you're doing.\n" - IBI_ARGS="--scopes=compute-rw,storage-rw,userinfo-email \"--min-cpu-platform=Intel Haswell\"" + IBI_ARGS="--scopes=compute-rw,storage-rw,userinfo-email" SSHUSER="centos" else unset IBI_ARGS diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 0a62ceb7c..752823634 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -1,8 +1,10 @@ package libpod import ( + "strings" "time" + "github.com/containers/image/manifest" "github.com/containers/libpod/libpod/driver" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -49,6 +51,53 @@ type InspectContainerData struct { ExitCommand []string `json:"ExitCommand"` Namespace string `json:"Namespace"` IsInfra bool `json:"IsInfra"` + Config *InspectContainerConfig `json:"Config"` +} + +// InspectContainerConfig holds further data about how a container was initially +// configured. +type InspectContainerConfig struct { + // Container hostname + Hostname string `json:"Hostname"` + // Container domain name - unused at present + DomainName string `json:"Domainname"` + // User the container was launched with + User string `json:"User"` + // Unused, at present + AttachStdin bool `json:"AttachStdin"` + // Unused, at present + AttachStdout bool `json:"AttachStdout"` + // Unused, at present + AttachStderr bool `json:"AttachStderr"` + // Whether the container creates a TTY + Tty bool `json:"Tty"` + // Whether the container leaves STDIN open + OpenStdin bool `json:"OpenStdin"` + // Whether STDIN is only left open once. + // Presently not supported by Podman, unused. + StdinOnce bool `json:"StdinOnce"` + // Container environment variables + Env []string `json:"Env"` + // Container command + Cmd []string `json:"Cmd"` + // Container image + Image string `json:"Image"` + // Unused, at present. I've never seen this field populated. + Volumes map[string]struct{} `json:"Volumes"` + // Container working directory + WorkingDir string `json:"WorkingDir"` + // Container entrypoint + Entrypoint string `json:"Entrypoint"` + // On-build arguments - presently unused. More of Buildah's domain. + OnBuild *string `json:"OnBuild"` + // Container labels + Labels map[string]string `json:"Labels"` + // Container annotations + Annotations map[string]string `json:"Annotations"` + // Container stop signal + StopSignal uint `json:"StopSignal"` + // Configured healthcheck for the container + Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` } // InspectMount provides a record of a single mount in a container. It contains @@ -192,7 +241,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) } } - mounts, err := c.getInspectMounts() + mounts, err := c.getInspectMounts(spec) if err != nil { return nil, err } @@ -284,6 +333,12 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) // Get information on the container's network namespace (if present) data = c.getContainerNetworkInfo(data) + inspectConfig, err := c.generateInspectContainerConfig(spec) + if err != nil { + return nil, err + } + data.Config = inspectConfig + if size { rootFsSize, err := c.rootFsSize() if err != nil { @@ -302,7 +357,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) // Get inspect-formatted mounts list. // Only includes user-specified mounts. Only includes bind mounts and named // volumes, not tmpfs volumes. -func (c *Container) getInspectMounts() ([]*InspectMount, error) { +func (c *Container) getInspectMounts(ctrSpec *spec.Spec) ([]*InspectMount, error) { inspectMounts := []*InspectMount{} // No mounts, return early @@ -319,7 +374,7 @@ func (c *Container) getInspectMounts() ([]*InspectMount, error) { for _, namedVol := range c.config.NamedVolumes { namedVolumes[namedVol.Dest] = namedVol } - for _, mount := range c.config.Spec.Mounts { + for _, mount := range ctrSpec.Mounts { mounts[mount.Destination] = mount } @@ -401,3 +456,56 @@ func parseMountOptionsForInspect(options []string, mount *InspectMount) { mount.Mode = zZ mount.Options = otherOpts } + +// Generate the InspectContainerConfig struct for the Config field of Inspect. +func (c *Container) generateInspectContainerConfig(spec *spec.Spec) (*InspectContainerConfig, error) { + ctrConfig := new(InspectContainerConfig) + + ctrConfig.Hostname = c.Hostname() + ctrConfig.User = c.config.User + if spec.Process != nil { + ctrConfig.Tty = spec.Process.Terminal + ctrConfig.Env = []string{} + for _, val := range spec.Process.Env { + ctrConfig.Env = append(ctrConfig.Env, val) + } + ctrConfig.WorkingDir = spec.Process.Cwd + } + + ctrConfig.OpenStdin = c.config.Stdin + ctrConfig.Image = c.config.RootfsImageName + + // Leave empty is not explicitly overwritten by user + if len(c.config.Command) != 0 { + ctrConfig.Cmd = []string{} + for _, val := range c.config.Command { + ctrConfig.Cmd = append(ctrConfig.Cmd, val) + } + } + + // Leave empty if not explicitly overwritten by user + if len(c.config.Entrypoint) != 0 { + ctrConfig.Entrypoint = strings.Join(c.config.Entrypoint, " ") + } + + if len(c.config.Labels) != 0 { + ctrConfig.Labels = make(map[string]string) + for k, v := range c.config.Labels { + ctrConfig.Labels[k] = v + } + } + + if len(spec.Annotations) != 0 { + ctrConfig.Annotations = make(map[string]string) + for k, v := range spec.Annotations { + ctrConfig.Annotations[k] = v + } + } + + ctrConfig.StopSignal = c.config.StopSignal + // TODO: should JSON deep copy this to ensure internal pointers don't + // leak. + ctrConfig.Healthcheck = c.config.HealthCheckConfig + + return ctrConfig, nil +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0be5427d9..55cc5089b 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -424,7 +424,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"} { + for _, dest := range []string{"/run", "/run/lock"} { if MountExists(mounts, dest) { continue } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index cb7411ce5..644a9ae86 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -149,6 +149,13 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. // Need to load in all the repo tags from the manifest res := []pullRefPair{} for _, dst := range manifest[0].RepoTags { + //check if image exists and gives a warning of untagging + localImage, err := ir.NewFromLocal(dst) + imageID := strings.TrimSuffix(manifest[0].Config, ".json") + if err == nil && imageID != localImage.ID() { + logrus.Errorf("the image %s already exists, renaming the old one with ID %s to empty string", dst, localImage.ID()) + } + pullInfo, err := ir.getPullRefPair(srcRef, dst) if err != nil { return nil, err @@ -168,7 +175,6 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. if err != nil { return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef) } - var dest string if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { // If the input image has no image.ref.name, we need to feed it a dest anyways diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 9bbefdb06..6e84c0759 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -246,7 +246,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res } logDriver := KubernetesLogging - if ctr.LogDriver() != "" { + if ctr.LogDriver() == JSONLogging { + logrus.Errorf("json-file logging specified but not supported. Choosing k8s-file logging instead") + } else if ctr.LogDriver() != "" { logDriver = ctr.LogDriver() } args = append(args, "-l", fmt.Sprintf("%s:%s", logDriver, ctr.LogPath())) diff --git a/logo/podman-logo-source.svg b/logo/podman-logo-source.svg index 7964b8326..9e5cc6625 100644 --- a/logo/podman-logo-source.svg +++ b/logo/podman-logo-source.svg @@ -1,73 +1,67 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="864.88531" - height="221.40775" - viewBox="0 0 228.83423 58.580801" + id="svg8" version="1.1" - id="svg194630" - inkscape:version="0.92.2 2405546, 2018-03-11" - sodipodi:docname="podman-source.svg"> + viewBox="0 0 228.25899 61.226642" + height="231.40778" + width="862.71112"> <defs - id="defs194624"> - <inkscape:path-effect - is_visible="true" - id="path-effect10334-3" - effect="spiro" /> - <inkscape:path-effect - effect="spiro" - id="path-effect10336-6" - is_visible="true" /> - <inkscape:path-effect - is_visible="true" - id="path-effect9986-7" - effect="spiro" /> - <inkscape:path-effect - effect="spiro" - id="path-effect9984-5" - is_visible="true" /> - <inkscape:path-effect - effect="spiro" - id="path-effect10300-3" - is_visible="true" /> - <inkscape:path-effect - is_visible="true" - id="path-effect10304-5" - effect="spiro" /> + id="defs2"> + <marker + style="overflow:visible" + id="marker5584" + refX="0" + refY="0" + orient="auto"> + <path + transform="scale(0.2)" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:1.00000003pt;stroke-opacity:1" + d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 Z" + id="path5582" /> + </marker> + <marker + style="overflow:visible" + id="DiamondS" + refX="0" + refY="0" + orient="auto"> + <path + transform="scale(0.2)" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:1.00000003pt;stroke-opacity:1" + d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 Z" + id="path5359" /> + </marker> + <marker + style="overflow:visible" + id="DotL" + refX="0" + refY="0" + orient="auto"> + <path + transform="matrix(0.8,0,0,0.8,5.92,0.8)" + style="fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:1.00000003pt;stroke-opacity:1" + d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z" + id="path5335" /> + </marker> + <clipPath + id="clipPath81511" + clipPathUnits="userSpaceOnUse"> + <rect + y="-1.4835175" + x="934.65692" + height="12.832292" + width="26.19375" + id="rect81513" + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:#a7a9ac;stroke-width:0.52916664;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> + </clipPath> </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.35" - inkscape:cx="71.014106" - inkscape:cy="693.56104" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - units="px" - fit-margin-top="20" - fit-margin-left="20" - fit-margin-right="20" - fit-margin-bottom="20" - inkscape:window-width="1712" - inkscape:window-height="899" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:window-maximized="1" /> <metadata - id="metadata194627"> + id="metadata5"> <rdf:RDF> <cc:Work rdf:about=""> @@ -79,529 +73,458 @@ </rdf:RDF> </metadata> <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(90.226649,-273.75722)"> + transform="translate(-1170.4453,517.6202)" + id="layer1"> <g - transform="translate(997.58958,163.05706)" - id="g194622"> - <text - id="text194074" - y="150.27197" - x="-1022.7543" + id="g163426" /> + <g + id="g10819" + transform="translate(0,-285.75012)"> + <g + id="text10671" style="font-style:normal;font-weight:normal;font-size:37.59195328px;line-height:22.55517006px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#892ca0;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - xml:space="preserve" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868"><tspan + aria-label="podman"> + <path + id="path80700" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Montserrat;-inkscape-font-specification:Montserrat;fill:#892ca0;fill-opacity:1;stroke-width:0.26458332px" - y="150.27197" - x="-1022.7543" - id="tspan194072" - sodipodi:role="line">pod<tspan - id="tspan194070" - style="fill:#000000">man</tspan></tspan></text> - <g - id="g194220" - transform="translate(-2257.9336,340.05556)"> + d="m 1249.8778,-209.9329 c -3.1202,0 -5.526,1.27812 -6.9921,3.60883 v -3.45846 h -4.3231 v 27.29175 h 4.3231 v -10.75129 c 1.4661,2.3307 3.8719,3.60882 7.0673,3.60882 5.7139,0 9.4731,-4.09752 9.4731,-10.07464 0,-6.0899 -3.8719,-10.22501 -9.5483,-10.22501 z m -0.9022,16.61564 c -3.5713,0 -6.0899,-2.66903 -6.0899,-6.541 0,-3.75919 2.5186,-6.46581 6.0899,-6.46581 3.5712,0 6.0899,2.74421 6.0899,6.46581 0,3.83438 -2.5187,6.541 -6.0899,6.541 z" /> <path - id="path194076" - d="m 1200.0567,-176.59543 -19.2981,-9.23285 -4.8204,-20.8616 13.2871,-16.78059 21.3893,-0.0641 13.3849,16.70113 -4.699,20.88969 z" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#892ca0;stroke-width:1.0583334;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - inkscape:connector-curvature="0" /> + id="path80702" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Montserrat;-inkscape-font-specification:Montserrat;fill:#892ca0;fill-opacity:1;stroke-width:0.26458332px" + d="m 1272.4964,-209.9329 c -6.2027,0 -10.4506,4.09752 -10.4506,10.14983 0,6.01471 4.2479,10.14982 10.4506,10.14982 6.1651,0 10.413,-4.13511 10.413,-10.14982 0,-6.05231 -4.2479,-10.14983 -10.413,-10.14983 z m 0,3.7216 c 3.5712,0 6.0899,2.63144 6.0899,6.46582 0,3.87197 -2.5187,6.50341 -6.0899,6.50341 -3.6088,0 -6.1275,-2.63144 -6.1275,-6.50341 0,-3.83438 2.5187,-6.46582 6.1275,-6.46582 z" /> <path - style="fill:none;fill-rule:evenodd;stroke:#cccccc;stroke-width:0.79375005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.95294118" - d="m 1189.7397,-221.65207 1.5045,2.52085" - id="path194078" - inkscape:connector-curvature="0" /> + id="path80704" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Montserrat;-inkscape-font-specification:Montserrat;fill:#892ca0;fill-opacity:1;stroke-width:0.26458332px" + d="m 1302.1564,-217.67684 v 11.35277 c -1.466,-2.33071 -3.8719,-3.64642 -7.1048,-3.64642 -5.6388,0 -9.5108,4.09752 -9.5108,10.11223 0,6.0899 3.8344,10.2626 9.5859,10.2626 3.1954,0 5.6012,-1.27812 7.0297,-3.60882 v 3.42086 h 4.3231 v -27.89322 z m -6.1275,24.47236 c -3.6464,0 -6.1274,-2.70662 -6.165,-6.50341 0.038,-3.83438 2.5562,-6.57859 6.165,-6.57859 3.6089,0 6.1275,2.70662 6.1275,6.57859 0,3.79679 -2.5186,6.50341 -6.1275,6.50341 z" /> <path - inkscape:connector-curvature="0" - id="path194080" - d="m 1209.7268,-221.94151 -1.4308,2.56335" - style="fill:none;fill-rule:evenodd;stroke:#cccccc;stroke-width:0.79375005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.95294118" /> - <g - id="g194084" - inkscape:transform-center-x="-17.08132" - inkscape:transform-center-y="5.3335262" - transform="rotate(106.71323,1247.9186,-137.82522)"> - <path - style="fill:none;fill-rule:evenodd;stroke:#cccccc;stroke-width:0.79375005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.95294118" - d="m 1189.7571,-94.267724 1.4678,2.542367" - id="path194082" - inkscape:connector-curvature="0" /> - </g> - <g - transform="matrix(0.28758168,0.95775612,0.95775612,-0.28758168,925.52985,-1372.6632)" - inkscape:transform-center-y="5.3335446" - inkscape:transform-center-x="17.08132" - id="g194088"> - <path - inkscape:connector-curvature="0" - id="path194086" - d="m 1189.7571,-94.267724 1.4678,2.542367" - style="fill:none;fill-rule:evenodd;stroke:#cccccc;stroke-width:0.79375005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.95294118" /> - </g> + id="path80706" + style="fill:#60605b" + d="m 1339.1334,-209.9329 c -3.6464,0 -6.3154,1.46608 -7.5936,4.81177 -0.9398,-3.04495 -3.3832,-4.81177 -6.9545,-4.81177 -3.3081,0 -5.8643,1.20294 -7.2552,4.02234 v -3.87197 h -4.2855 v 19.99891 h 4.2855 v -9.88668 c 0,-3.49605 2.1051,-6.12749 5.5636,-6.24026 2.9322,0 4.699,1.842 4.699,4.88695 v 11.23999 h 4.3231 v -9.88668 c 0,-3.49605 2.0675,-6.12749 5.4884,-6.24026 2.9321,0 4.7366,1.842 4.7366,4.88695 v 11.23999 h 4.323 v -12.44293 c 0,-4.77418 -2.7442,-7.70635 -7.3304,-7.70635 z" /> + <path + id="path80708" + style="fill:#60605b" + d="m 1368.4916,-189.78362 -0.038,-13.11959 c -0.038,-4.39826 -2.9322,-7.02969 -8.0823,-7.02969 -3.7968,0 -5.8268,0.86461 -8.6086,2.51866 l 1.7669,3.00736 c 1.9923,-1.35332 3.9847,-2.02997 5.9019,-2.02997 3.1577,0 4.7742,1.50368 4.7742,4.02234 v 0.60147 h -6.0523 c -4.8118,0.0376 -7.556,2.36829 -7.556,6.01471 0,3.53365 2.7066,6.20267 7.0297,6.20267 2.8946,0 5.1877,-0.86461 6.6162,-2.63143 v 2.44347 z m -9.9619,-3.2329 c -2.4059,0 -3.872,-1.16535 -3.872,-2.96977 0,-1.91719 1.3157,-2.66903 4.1351,-2.66903 h 5.4133 v 1.84201 c -0.2256,2.18033 -2.5939,3.79679 -5.6764,3.79679 z" /> + <path + id="path80710" + style="fill:#60605b" + d="m 1385.9695,-209.9329 c -3.3833,0 -5.9396,1.20294 -7.3681,3.98474 v -3.83437 h -4.323 v 19.99891 h 4.323 v -10.63852 c 0.3008,-3.12013 2.3683,-5.41324 5.6764,-5.45083 3.0074,0 4.8118,1.842 4.8118,4.84936 v 11.23999 h 4.3231 v -12.44293 c 0,-4.77418 -2.7818,-7.70635 -7.4432,-7.70635 z" /> + </g> + </g> + <g + id="g10987" + transform="translate(-4.0076941e-7,-285.75012)"> + <g + id="g10985" + transform="translate(238.41304,-199.74894)"> + <path + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 963.98321,-1.2653795 h 12.72207" + id="path10821" /> <path - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1217.3928,-198.86664 c -1.6026,0.3763 -2.7924,0.4881 -4.4867,0.49355" - id="path194090" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> + id="path10823" + d="m 966.09989,1.3804536 h 12.72207" + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> <g - style="stroke-width:1.07322586" - transform="matrix(0.93201038,0,0,0.93153044,84.668387,39.521136)" - id="g194148"> + style="stroke-width:0.86916679" + transform="matrix(1.1500458,0,0,1.1510087,-143.9252,0.43908228)" + id="g10875"> <path - style="opacity:1;fill:#d7d8da;fill-opacity:1;stroke:#000000;stroke-width:0.85187292;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1196.4002,-277.28841 c -6.3145,0 -11.4334,5.11824 -11.4334,11.4319 0,5.60953 0.1555,10.87478 -3.8973,11.52796 2.2201,1.74955 31.1822,1.47773 31.1822,0.0657 -3.9446,-0.40986 -3.9311,-5.05055 -4.1745,-11.47895 -0.2435,-6.42839 -5.3624,-11.54663 -11.677,-11.54663 z" - id="path194092" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sccccs" /> + id="path10825" + d="m 973.10356,3.3983398 c -2.30839,-3.74257021 -1.79145,-7.3525044 -2.88299,-12.510239 -0.59897,-3.1855248 -2.62142,-5.8149598 -5.25403,-6.7776908 -2.17189,-0.757647 -6.68263,-0.759955 -8.59472,0 -2.63261,0.962731 -4.65498,3.592166 -5.25395,6.7776908 -1.09155,5.1577346 -0.57458,8.76766879 -2.88297,12.510239" + style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.68990111;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <ellipse + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" + id="ellipse10827" + cx="966.56354" + cy="-4.1070371" + rx="1.1158856" + ry="1.1746163" /> + <ellipse + ry="1.1746163" + rx="1.1158856" + cy="-4.1070371" + cx="955.28723" + id="ellipse10829" + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> + <g + id="g10839" + style="stroke:#a7a9ac;stroke-width:0.51800275;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(0.88789899,0,0,0.88789899,108.02196,0.55783395)"> + <path + id="path10831" + d="m 956.41893,-5.9814347 -4.67056,1.2514712" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10833" + d="m 956.9481,-5.2538306 -4.6966,2.711577" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10835" + d="m 957.34497,-4.3939348 -4.7625,4.76249998" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10837" + d="m 957.5434,-3.2694556 -2.434,4.21584432" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(-0.88789899,0,0,0.88789899,1814.1581,0.55783389)" + style="stroke:#a7a9ac;stroke-width:0.51800275;stroke-miterlimit:4;stroke-dasharray:none" + id="g10849"> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 956.41893,-5.9814347 -4.67056,1.2514712" + id="path10841" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 956.9481,-5.2538306 -4.6966,2.711577" + id="path10843" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 957.34497,-4.3939348 -4.7625,4.76249998" + id="path10845" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.51800275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 957.5434,-3.2694556 -2.434,4.21584432" + id="path10847" /> + </g> <ellipse - ry="1.2784375" - rx="1.2185711" - cy="-221.69182" - cx="1196.4897" - id="ellipse194094" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.85187298;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - transform="matrix(0.99938194,-0.03515294,0.03512104,0.99938307,0,0)" /> + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" + id="ellipse10851" + cx="961.04285" + cy="-4.9292688" + rx="3.5825801" + ry="3.4063873" /> <ellipse - ry="3.3919933" - rx="3.8366725" - cy="-224.76184" - cx="1205.7982" - id="ellipse194096" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:0.85187823;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - transform="matrix(0.99946418,-0.03273147,0.0377188,0.99928839,0,0)" /> + transform="scale(-1,1)" + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10853" + cx="-966.52734" + cy="-7.8406329" + rx="1.7252614" + ry="1.7690334" /> <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - sodipodi:nodetypes="csc" - inkscape:original-d="m 1203.9748,-270.3367 c -0.4929,-0.18136 -0.8779,-0.48319 -1.375,-0.66415 -0.4972,-0.18083 -0.9228,0.4235 -1.3258,0.57498" - inkscape:path-effect="#path-effect10334-3" - inkscape:connector-curvature="0" - id="path194098" - d="m 1203.9748,-270.3367 c -0.3302,-0.40944 -0.849,-0.66007 -1.375,-0.66415 -0.4959,-0.004 -0.9897,0.21029 -1.3258,0.57498" - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.56791532;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.68990105;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 968.07427,-11.544209 c -0.34474,-0.448317 -0.89334,-0.733231 -1.45836,-0.757402 -0.53344,-0.02282 -1.07281,0.185834 -1.45201,0.561706" + id="path10855" /> <g - transform="matrix(0.69366503,-0.00293008,0.00289825,0.69135666,529.16872,-257.19815)" - style="stroke-width:0.82007539;stroke-miterlimit:4;stroke-dasharray:none" - id="g194106"> + id="g10863" + style="stroke-width:0.77700406;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(0.88789899,0,0,0.88789899,106.61242,0.08798743)"> <g + id="g10861" transform="translate(0,-0.52916667)" - id="g194104" - style="stroke-width:0.82007539;stroke-miterlimit:4;stroke-dasharray:none"> + style="stroke-width:0.77700406"> <path - sodipodi:nodetypes="cccc" - inkscape:connector-curvature="0" - id="path194100" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.77700406;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 962.30591,-5.7829972 0.0993,1.9843749 c 0,0 1.58751,1.4221355 2.51355,-0.033073 0,0 -0.0993,-0.8268214 -0.16541,-1.0914047" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.82007539;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path10857" /> <path - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.82007539;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path10859" d="m 962.28751,-5.7829972 -0.0993,1.9843749 c 0,0 -1.58751,1.4221355 -2.51355,-0.033073 0,0 0.0993,-0.8268214 0.16541,-1.0914047" - id="path194102" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cccc" /> + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.77700406;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> </g> <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - sodipodi:nodetypes="cszsccc" - inkscape:connector-curvature="0" - d="m 1197.9362,-265.07184 c -0.1012,-0.25799 -0.1029,-0.77483 -0.4026,-0.77357 -0.2995,0.002 -0.5774,-0.1914 -0.872,-0.19011 -0.2946,0.002 -0.571,0.19636 -0.8707,0.19765 -0.2993,0.002 -0.2975,0.51817 -0.396,0.77704 -0.1011,0.25897 1.275,1.09288 1.275,1.09288 0,0 1.3673,-0.84575 1.2663,-1.10389 z" - style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.56791532;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="path194108" /> + id="path10865" + style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 962.57174,-5.5436767 c -0.1202,-0.3110186 -0.1202,-0.9330691 -0.48062,-0.9330691 -0.36047,0 -0.69372,-0.2332673 -1.04826,-0.2332673 -0.35456,0 -0.68781,0.2332673 -1.04828,0.2332673 -0.36046,0 -0.36046,0.6220505 -0.48061,0.9330691 -0.1202,0.3110253 1.52889,1.3218472 1.52889,1.3218472 0,0 1.64903,-1.0108219 1.52888,-1.3218472 z" /> + <ellipse + ry="1.7690334" + rx="1.7252614" + cy="-7.8406329" + cx="955.36047" + id="ellipse10867" + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <ellipse + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10869" + cx="954.78467" + cy="-8.3384542" + rx="0.8483994" + ry="0.88143349" /> + <path + id="path10871" + d="m 953.81353,-11.544209 c 0.34474,-0.448315 0.89333,-0.73323 1.45835,-0.757402 0.53344,-0.02282 1.07281,0.185833 1.45201,0.561706" + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.68990105;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <ellipse + ry="0.88143349" + rx="0.8483994" + cy="-8.3384542" + cx="967.05286" + id="ellipse10873" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.68990111;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + id="path10877" + d="m 948.21183,4.2931494 h 32.92765" + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + style="stroke-width:1.20411575" + transform="matrix(0.83048493,0,0,0.83048493,164.55035,-1.4237955)" + clip-path="url(#clipPath81511)" + id="g10929"> <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.56791532;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 1188.9919,-270.28459 c 0.3005,-0.43161 0.8005,-0.71808 1.3248,-0.75912 0.4944,-0.0387 1.002,0.14023 1.3629,0.48043" - id="path194110" - inkscape:connector-curvature="0" - inkscape:path-effect="#path-effect10336-6" - inkscape:original-d="m 1188.9919,-270.28459 c 0.479,-0.21554 0.8417,-0.54368 1.3248,-0.75912 0.4832,-0.21531 0.9505,0.35763 1.3629,0.48043" - sodipodi:nodetypes="csc" /> + id="path10879" + d="m 958.31594,13.737399 c -0.26199,-1.782196 -0.38924,-3.720848 -0.8727,-6.0017375 -0.53058,-2.8174518 -2.32212,-5.1430676 -4.65415,-5.9945597 -1.92393,-0.6701043 -5.91966,-0.6721461 -7.61343,0 -2.33204,0.8514921 -4.1235,3.1771079 -4.65409,5.9945597 -0.48346,2.2808895 -0.61071,4.2195415 -0.87269,6.0017375" + style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.95576686;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <ellipse + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" + id="ellipse10881" + cx="954.20367" + cy="12.16224" + rx="0.98848081" + ry="1.0388949" /> + <ellipse + ry="1.0388949" + rx="0.98848081" + cy="12.16224" + cx="944.21478" + id="ellipse10883" + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> <g - style="stroke-width:1.07322586" - id="g194116" - transform="translate(-0.1322934,-148.99351)"> - <ellipse - transform="matrix(-0.99938215,0.03514721,0.03512674,0.99938287,0,0)" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.8518728;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194112" - cx="-1206.3459" - cy="-75.635956" - rx="1.8695227" - ry="1.9430941" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - transform="matrix(0.99938215,-0.03514721,0.03512674,0.99938287,0,0)" - ry="0.96816051" - rx="0.91933995" - cy="-76.182663" - cx="1206.9153" - id="ellipse194114" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.8518728;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> + id="g10893" + style="stroke:#a7a9ac;stroke-width:0.81074655;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(0.7865243,0,0,0.7853064,193.68501,16.288101)"> + <path + id="path10885" + d="m 956.41893,-5.9814347 -4.67056,1.2514712" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10887" + d="m 956.9481,-5.2538306 -4.6966,2.711577" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10889" + d="m 957.34497,-4.3939348 -4.7625,4.76249998" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10891" + d="m 957.5434,-3.2694556 -2.434,4.21584432" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> <g - transform="matrix(-0.86763199,-0.04368486,-0.04400126,0.8648962,2029.5877,-215.68906)" - style="stroke:#60605b;stroke-width:0.65475416;stroke-miterlimit:4;stroke-dasharray:none" - id="g194126"> - <g - id="g194124" - style="stroke-width:0.65475416"> - <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 956.32834,-6.2874931 c -2.17461,0.00852 -3.90258,0.727992 -5.25392,1.62519" - id="path194118" - inkscape:connector-curvature="0" /> - <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 957.00529,-5.3889116 c -2.45021,0.3102076 -3.99116,1.6666651 -5.21618,2.999397" - id="path194120" - inkscape:connector-curvature="0" /> - <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 957.14608,-4.4502461 c -2.0377,0.8062397 -3.03766,2.4684328 -3.7999,4.05763317" - id="path194122" - inkscape:connector-curvature="0" /> - </g> + transform="matrix(-0.7865243,0,0,0.7853064,1705.0252,16.288101)" + style="stroke:#a7a9ac;stroke-width:0.81074655;stroke-miterlimit:4;stroke-dasharray:none" + id="g10903"> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 956.41893,-5.9814347 -4.67056,1.2514712" + id="path10895" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 956.9481,-5.2538306 -4.6966,2.711577" + id="path10897" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 957.34497,-4.3939348 -4.7625,4.76249998" + id="path10899" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.81074655;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 957.5434,-3.2694556 -2.434,4.21584432" + id="path10901" /> </g> + <ellipse + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" + id="ellipse10905" + cx="949.31329" + cy="11.435012" + rx="3.1735437" + ry="3.0127952" /> + <ellipse + transform="scale(-1,1)" + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10907" + cx="-954.17163" + cy="8.8600426" + rx="1.528282" + ry="1.5646298" /> + <path + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.9557668;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 955.54199,5.5843938 c -0.30575,-0.3965787 -0.79155,-0.6484892 -1.29185,-0.6698876 -0.4724,-0.020205 -0.95006,0.1642901 -1.28623,0.4968037" + id="path10909" /> <g - id="g194136" - style="stroke:#60605b;stroke-width:0.65475416;stroke-miterlimit:4;stroke-dasharray:none" - transform="matrix(0.86340059,-0.09610492,0.09623303,0.86064955,368.1354,-165.50024)"> + id="g10917" + style="stroke-width:1.21611977;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(0.7865243,0,0,0.7853064,192.4364,15.872544)"> <g - style="stroke-width:0.65475416" - id="g194134"> + style="stroke-width:1.21611977" + id="g10915" + transform="translate(0,-0.52916667)"> <path - inkscape:connector-curvature="0" - id="path194128" - d="m 956.32834,-6.2874931 c -2.17461,0.00852 -3.90258,0.727992 -5.25392,1.62519" - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" - id="path194130" - d="m 957.00529,-5.3889116 c -2.45021,0.3102076 -3.99116,1.6666651 -5.21618,2.999397" - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.21611977;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 962.30591,-5.7829972 0.0993,1.9843749 c 0,0 1.58751,1.4221355 2.51355,-0.033073 0,0 -0.0993,-0.8268214 -0.16541,-1.0914047" + id="path10911" /> <path - inkscape:connector-curvature="0" - id="path194132" - d="m 956.99206,-4.4197862 c -2.0377,0.8062397 -3.03766,2.4684328 -3.7999,4.05763312" - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.65475416;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + id="path10913" + d="m 962.28751,-5.7829972 -0.0993,1.9843749 c 0,0 -1.58751,1.4221355 -2.51355,-0.033073 0,0 0.0993,-0.8268214 0.16541,-1.0914047" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.21611977;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> </g> - <g - style="stroke-width:1.07322586" - id="g194142" - transform="translate(-0.1322934,-149.16808)"> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - ry="1.9430944" - rx="1.8695223" - cy="-87.545464" - cx="1193.2991" - id="ellipse194138" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.8518728;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - transform="matrix(0.99967764,-0.0253894,0.02538424,0.99967777,0,0)" /> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.8518728;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194140" - cx="-1192.7297" - cy="-88.092163" - rx="0.91933978" - ry="0.96816063" - transform="matrix(-0.99967764,0.0253894,0.02538424,0.99967777,0,0)" /> - </g> + <path + id="path10919" + style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 950.66771,10.891591 c -0.10648,-0.275082 -0.10648,-0.825257 -0.42575,-0.825257 -0.31931,0 -0.61451,-0.2063147 -0.92858,-0.2063147 -0.31407,0 -0.60928,0.2063147 -0.92858,0.2063147 -0.31932,0 -0.31932,0.550175 -0.42575,0.825257 -0.10647,0.275088 1.35433,1.169114 1.35433,1.169114 0,0 1.46076,-0.894026 1.35433,-1.169114 z" /> <ellipse - transform="matrix(0.99938194,-0.03515294,0.03512104,0.99938307,0,0)" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.85187298;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - id="ellipse194144" - cx="1214.1729" - cy="-221.06982" - rx="1.2185711" - ry="1.2784375" /> + ry="1.5646298" + rx="1.528282" + cy="8.8600426" + cx="944.27972" + id="ellipse10921" + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <ellipse + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10923" + cx="943.76965" + cy="8.4197426" + rx="0.75153452" + ry="0.77958798" /> <path - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.85187304;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1196.6606,-258.19566 a 8.2351559,7.2429689 0 0 0 -7.6956,4.68912 c 4.7591,0.2089 10.6723,0.19768 15.4021,0.0114 a 8.2351559,7.2429689 0 0 0 -7.7065,-4.7005 z" - id="path194146" - inkscape:connector-curvature="0" /> + id="path10925" + d="m 942.90945,5.5843938 c 0.30575,-0.3965787 0.79155,-0.6484892 1.29185,-0.6698876 0.4724,-0.020205 0.95006,0.1642901 1.28623,0.4968037" + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.9557668;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <ellipse + ry="0.77958798" + rx="0.75153452" + cy="8.4197426" + cx="954.63715" + id="ellipse10927" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.95576686;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> </g> <path - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1219.0623,-197.2163 c -3.5719,1.43037 -11.1149,1.14699 -18.2221,1.17499 -7.1072,0.028 -15.5965,-0.27284 -17.0071,-1.69572" - id="path194150" - inkscape:connector-curvature="0" - sodipodi:nodetypes="csc" /> - <path - style="opacity:1;fill:#d7d8da;fill-opacity:1;stroke:#000000;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1198.4754,-191.15273 c 0,1.00704 0,1.99863 -0.017,2.94659 6.3077,1.15754 12.6043,1.18012 18.5208,0.0181 -0.042,-0.89578 -0.071,-1.85458 -0.1064,-2.86597 -0.1939,-5.53538 -4.2687,-9.41337 -9.2955,-9.41338 -5.0269,-1e-5 -9.1019,3.87807 -9.1019,9.31466 z" - id="path194152" - inkscape:connector-curvature="0" - sodipodi:nodetypes="sccczs" /> - <ellipse - ry="1.1909043" - rx="1.1357201" - cy="-148.3954" - cx="1206.6315" - id="ellipse194154" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - transform="matrix(0.99938258,-0.03513486,0.03513911,0.99938243,0,0)" /> + style="opacity:1;fill:none;fill-opacity:1;stroke:#892ca0;stroke-width:1.0583334;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 961.97165,23.28467 -19.29817,-9.23285 -4.82036,-20.8616002 13.2871,-16.7806158 21.38926,-0.06408 13.38485,16.7011458 -4.69887,20.8897002 z" + id="path10931" /> <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - sodipodi:nodetypes="csc" - inkscape:original-d="m 1213.5138,-196.08059 c -0.3776,-0.13889 -0.6726,-0.37001 -1.0534,-0.5086 -0.3809,-0.13847 -0.7069,0.32432 -1.0158,0.4403" - inkscape:path-effect="#path-effect9986-7" - inkscape:connector-curvature="0" - id="path194156" - d="m 1213.5138,-196.08059 c -0.253,-0.31355 -0.6505,-0.50546 -1.0534,-0.5086 -0.3799,-0.003 -0.7582,0.161 -1.0158,0.4403" - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 943.44933,8.5264827 h 19.83144" + id="path10933" /> <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 1202.0344,-196.04037 c 0.2304,-0.33054 0.6134,-0.54988 1.0151,-0.5813 0.3788,-0.0296 0.7676,0.10738 1.0442,0.36791" - id="path194158" - inkscape:connector-curvature="0" - inkscape:path-effect="#path-effect9984-5" - inkscape:original-d="m 1202.0344,-196.04037 c 0.3671,-0.16505 0.645,-0.41631 1.0151,-0.5813 0.3701,-0.1649 0.7281,0.27386 1.0442,0.36791" - sodipodi:nodetypes="csc" /> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - ry="1.4879755" - rx="1.432372" - cy="-150.7608" - cx="-1218.6735" - id="ellipse194160" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374987;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - transform="matrix(-0.99938278,0.03512913,0.03514482,0.99938223,0,0)" /> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374987;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194162" - cx="1219.1096" - cy="-151.17946" - rx="0.70437056" - ry="0.74139434" - transform="matrix(0.99938278,-0.03512913,0.03514482,0.99938223,0,0)" /> - <path - id="path194164" - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1218.7033,-188.67623 -1.6617,0.47913 c -5.9787,1.17108 -12.2753,1.1485 -18.583,-0.009 l -2.5256,-0.47009" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cccc" /> + id="path10935" + d="m 952.87074,12.492952 h 27.50208" + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> <g - transform="matrix(-0.54020821,-0.0266189,-0.02739623,0.52701539,1727.0536,-162.01736)" - style="stroke:#60605b;stroke-width:0.99047768;stroke-miterlimit:4;stroke-dasharray:none" - id="g194168"> + transform="translate(0.78045403,0.88370984)" + id="g10979"> <path - inkscape:connector-curvature="0" - d="m 957.14608,-4.4502461 c -2.0377,0.8062397 -3.03766,2.4684328 -3.7999,4.05763317 m 3.65911,-4.99629867 c -2.45021,0.3102076 -3.99116,1.6666651 -5.21618,2.999397 m 4.53923,-3.8979785 c -2.17461,0.00852 -3.90258,0.727992 -5.25392,1.62519" - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.99047768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path194166" /> - </g> - <g - transform="matrix(0.53757364,-0.05856052,0.05991696,0.52442773,691.53583,-131.42451)" - id="g194176" - style="stroke:#60605b;stroke-width:0.99047768;stroke-miterlimit:4;stroke-dasharray:none"> + style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.79374999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 977.46866,11.110796 c -0.23262,-1.5824091 -0.34561,-3.3037374 -0.77487,-5.3289381 -0.4711,-2.5016132 -2.06181,-4.566525 -4.13242,-5.32256411 -1.70825,-0.59498494 -5.25606,-0.59679785 -6.75995,0 -2.07062,0.75603911 -3.66125,2.82095091 -4.13236,5.32256411 -0.42927,2.0252007 -0.54225,3.746529 -0.77486,5.3289381" + id="path10937" /> + <ellipse + ry="0.92243373" + rx="0.87767112" + cy="9.7122078" + cx="973.81744" + id="ellipse10939" + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> + <ellipse + style="opacity:1;fill:#e7e8e9;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" + id="ellipse10941" + cx="964.9483" + cy="9.7122078" + rx="0.87767112" + ry="0.92243373" /> + <g + transform="matrix(0.69835413,0,0,0.69727276,299.02348,11.966021)" + style="stroke:#a7a9ac;stroke-width:0.7583214;stroke-miterlimit:4;stroke-dasharray:none" + id="g10947"> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.7583214;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 955.74614,-3.9599306 -4.67056,1.2514712" + id="path10943" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.7583214;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 956.27531,-3.2323265 -4.6966,2.71157695" + id="path10945" /> + </g> + <g + id="g10953" + style="stroke:#a7a9ac;stroke-width:0.7583214;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(-0.69835413,0,0,0.69727276,1640.4712,13.375562)"> + <path + id="path10949" + d="m 956.41893,-5.9814347 -4.67056,1.2514712" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.7583214;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path10951" + d="m 956.9481,-5.2538306 -4.6966,2.711577" + style="fill:none;fill-rule:evenodd;stroke:#a7a9ac;stroke-width:0.7583214;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <ellipse + ry="2.6750579" + rx="2.8177862" + cy="9.0665035" + cx="969.47528" + id="ellipse10955" + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> + <ellipse + ry="1.3892332" + rx="1.3569601" + cy="6.7801905" + cx="-973.789" + id="ellipse10957" + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="scale(-1,1)" /> <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.99047768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 956.32834,-6.2874931 c -2.17461,0.00852 -3.90258,0.727992 -5.25392,1.62519" - id="path194170" - inkscape:connector-curvature="0" /> + id="path10959" + d="m 975.00567,3.8717489 c -0.27147,-0.3521218 -0.70281,-0.575793 -1.14703,-0.5947926 -0.41945,-0.01794 -0.84356,0.1458731 -1.14204,0.4411116" + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.79374993;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + transform="matrix(0.69835413,0,0,0.69727276,297.44499,13.006589)" + style="stroke-width:1.13748205;stroke-miterlimit:4;stroke-dasharray:none" + id="g10967"> + <g + transform="translate(0,-0.52916667)" + id="g10965" + style="stroke-width:1.13748205"> + <path + id="path10961" + d="m 962.30591,-5.7829972 0.0993,1.9843749 c 0,0 1.58751,1.4221355 2.51355,-0.033073 0,0 -0.0993,-0.8268214 -0.16541,-1.0914047" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.13748205;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.13748205;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 962.28751,-5.7829972 -0.0993,1.9843749 c 0,0 -1.58751,1.4221355 -2.51355,-0.033073 0,0 0.0993,-0.8268214 0.16541,-1.0914047" + id="path10963" /> + </g> + </g> <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.99047768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 957.00529,-5.3889116 c -2.45021,0.3102076 -3.99116,1.6666651 -5.21618,2.999397" - id="path194172" - inkscape:connector-curvature="0" /> + d="m 970.6778,8.5840058 c -0.0945,-0.2442451 -0.0945,-0.7327449 -0.37802,-0.7327449 -0.28351,0 -0.54562,-0.1831865 -0.82448,-0.1831865 -0.27887,0 -0.54098,0.1831865 -0.82449,0.1831865 -0.28352,0 -0.28352,0.4884998 -0.37802,0.7327449 -0.0945,0.2442506 1.20251,1.0380563 1.20251,1.0380563 0,0 1.297,-0.7938057 1.2025,-1.0380563 z" + style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path10969" /> + <ellipse + style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10971" + cx="965.00598" + cy="6.7801905" + rx="1.3569601" + ry="1.3892332" /> + <ellipse + ry="0.69219536" + rx="0.66728675" + cy="6.3892479" + cx="964.5531" + id="ellipse10973" + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> <path - style="fill:none;fill-rule:evenodd;stroke:#60605b;stroke-width:0.99047768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 956.99206,-4.4197862 c -2.0377,0.8062397 -3.03766,2.4684328 -3.7999,4.05763312" - id="path194174" - inkscape:connector-curvature="0" /> + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.79374993;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 963.78925,3.8717489 c 0.27148,-0.3521208 0.70281,-0.5757916 1.14703,-0.5947926 0.41945,-0.017942 0.84357,0.1458717 1.14205,0.4411116" + id="path10975" /> + <ellipse + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="ellipse10977" + cx="974.20233" + cy="6.3892479" + rx="0.66728675" + ry="0.69219536" /> </g> - <ellipse - transform="matrix(0.99967797,-0.02537634,0.02539731,0.99967744,0,0)" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374987;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194178" - cx="1207.7616" - cy="-162.88258" - rx="1.4323721" - ry="1.4879751" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - transform="matrix(-0.99967797,0.02537634,0.02539731,0.99967744,0,0)" - ry="0.74139422" - rx="0.70437062" - cy="-163.30124" - cx="-1207.3254" - id="ellipse194180" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374987;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - transform="matrix(0.99938258,-0.03513486,0.03513911,0.99938243,0,0)" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - id="ellipse194182" - cx="1221.1841" - cy="-147.88377" - rx="1.1357201" - ry="1.1909043" /> - <ellipse - ry="2.7544067" - rx="2.9848497" - cy="-149.08769" - cx="1214.015" - id="ellipse194184" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:0.79375076;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - transform="matrix(0.99941617,-0.03416617,0.0361353,0.99934691,0,0)" /> - <path - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - sodipodi:nodetypes="cscsccc" - inkscape:connector-curvature="0" - d="m 1209.0947,-191.32793 c -0.094,-0.24033 -0.1023,-0.66114 -0.3752,-0.72061 l -0.8127,-0.17709 -0.8115,0.18412 c -0.2721,0.0618 -0.2773,0.48269 -0.3691,0.72384 -0.094,0.24123 1.1883,1.01805 1.1883,1.01805 0,0 1.2743,-0.78785 1.1802,-1.02831 z" - style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="path194186" /> - <path - style="opacity:1;fill:#ff8080;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1207.7699,-190.02101 c -0.07,0.11114 -0.1741,0.2003 -0.2946,0.25322 -0.1288,0.0562 -0.2743,0.0709 -0.4134,0.0495 -0.098,-0.0151 -0.1933,-0.0497 -0.2806,-0.0977 0,0.005 0,0.01 0,0.015 0,0.62103 0.5035,1.12448 1.1245,1.12448 0.621,0 1.1244,-0.50345 1.1244,-1.12448 10e-5,-0.005 10e-5,-0.008 0,-0.0129 -0.087,0.047 -0.1801,0.0808 -0.2774,0.0956 -0.1391,0.0212 -0.2847,0.007 -0.4135,-0.0495 -0.1204,-0.0529 -0.2242,-0.14208 -0.2945,-0.25322 -0.1839,-0.45256 -0.15,-0.33357 -0.2749,0 z" - id="path194188" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cccccscccccc" /> - <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - d="m 1208.9056,-185.61743 c -7.1727,0.14599 -10.811,-0.41013 -16.8091,-1.86816" - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path194190" /> - <path - id="path194192" - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1210.5635,-183.66873 c -3.2099,0.35365 -5.8695,0.42038 -8.7157,0.008" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - sodipodi:nodetypes="sccczs" - inkscape:connector-curvature="0" - id="path194194" - d="m 1182.9959,-193.76305 c 0,0.81341 0,1.61437 -0.013,2.38006 4.5482,0.93499 9.0885,0.95323 13.3546,0.0146 -0.029,-0.72355 -0.052,-1.49801 -0.077,-2.31495 -0.1398,-4.47113 -3.2525,-6.51948 -6.6971,-6.51948 -3.4446,0 -6.5675,2.04843 -6.5675,6.43977 z" - style="opacity:1;fill:#d7d8da;fill-opacity:1;stroke:#000000;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <path - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 1193.6324,-197.30544 c -0.184,-0.21557 -0.4648,-0.34527 -0.7482,-0.34568 -0.2696,-3.9e-4 -0.5374,0.11563 -0.7215,0.31258" - id="path194196" - inkscape:connector-curvature="0" - inkscape:path-effect="#path-effect10300-3" - inkscape:original-d="m 1193.6324,-197.30544 c -0.2681,-0.0673 -0.4777,-0.27852 -0.7482,-0.34568 -0.2705,-0.0671 -0.5021,0.25638 -0.7215,0.31258" - sodipodi:nodetypes="csc" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <path - sodipodi:nodetypes="csc" - inkscape:original-d="m 1185.4425,-197.28587 c 0.2607,-0.08 0.4581,-0.30096 0.721,-0.38091 0.2629,-0.0799 0.5172,0.23193 0.7416,0.27751" - inkscape:path-effect="#path-effect10304-5" - inkscape:connector-curvature="0" - id="path194198" - d="m 1185.4425,-197.28587 c 0.171,-0.22212 0.4411,-0.36483 0.721,-0.38091 0.2712,-0.0156 0.5472,0.0877 0.7416,0.27751" - style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - transform="matrix(-0.99939588,0.03475453,0.03552362,0.99936884,0,0)" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194200" - cx="-1199.4635" - cy="-153.15971" - rx="1.2560792" - ry="1.2909421" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - transform="matrix(0.99939588,-0.03475453,0.03552362,0.99936884,0,0)" - ry="0.64322108" - rx="0.6176784" - cy="-153.52293" - cx="1199.8459" - id="ellipse194202" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - ry="1.2909336" - rx="1.2560872" - cy="-164.91208" - cx="1190.6571" - id="ellipse194204" - style="fill:#000000;fill-opacity:1;stroke:#892ca0;stroke-width:0.79374993;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - transform="matrix(0.99968481,-0.02510557,0.02567121,0.99967044,0,0)" /> - <path - sodipodi:nodetypes="cccccc" - inkscape:connector-curvature="0" - id="path194206" - transform="matrix(0.26458334,0,0,0.26458334,619.60435,-79.242757)" - d="m 2154.0312,-434.57031 c -4.4701,0.15199 -7.9731,3.60605 -7.8242,7.71484 0.052,1.39375 0.5293,4.24714 1.377,5.40104 4.4988,0.24291 8.9701,0.26971 13.3222,0.0957 1.0211,-1.32719 1.5437,-4.42574 1.4883,-6.04752 -0.1491,-4.10861 -3.8934,-7.31601 -8.3633,-7.16406 z" - style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#60605b;stroke-width:2.99999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" /> - <ellipse - inkscape:export-ydpi="89.962868" - inkscape:export-xdpi="89.962868" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.79374993;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - id="ellipse194208" - cx="-1190.2747" - cy="-165.2753" - rx="0.61768228" - ry="0.64321685" - transform="matrix(-0.99968481,0.02510557,0.02567121,0.99967044,0,0)" /> - <path - id="path194210" - style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.5291667;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" - d="m 1190.4633,-192.29709 c -0.069,-0.17514 -0.076,-0.4818 -0.2773,-0.52514 l -0.6006,-0.12906 -0.5998,0.13418 c -0.2011,0.045 -0.2049,0.35176 -0.2728,0.52749 -0.069,0.1758 0.8783,0.7419 0.8783,0.7419 0,0 0.9418,-0.57414 0.8722,-0.74937 z" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cscsccc" - inkscape:export-xdpi="89.962868" - inkscape:export-ydpi="89.962868" /> <path - sodipodi:nodetypes="cc" - inkscape:connector-curvature="0" - id="path194212" - d="m 1181.9907,-189.53092 c 2.7637,0.72901 4.8155,0.94562 7.7375,0.95618" - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - <ellipse - transform="matrix(0.99938258,-0.03513486,0.03513911,0.99938243,0,0)" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - id="ellipse194214" - cx="1199.9076" - cy="-149.77843" - rx="0.93498713" - ry="0.98041773" /> - <ellipse - ry="0.98041773" - rx="0.93498713" - cy="-150.07587" - cx="1191.4463" - id="ellipse194216" - style="opacity:1;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.99999952;stroke-opacity:1" - transform="matrix(0.99938258,-0.03513486,0.03513911,0.99938243,0,0)" /> + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 950.75405,15.138787 h 12.72207" + id="path10981" /> <path - style="opacity:1;fill:none;fill-opacity:1;stroke:#3c6eb4;stroke-width:0.79374993;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 1181.2324,-191.77986 c 5.5275,1.45801 11.8484,1.34886 16.4006,0.10007" - id="path194218" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> + id="path10983" + d="m 958.16237,17.784622 h 10.477" + style="fill:none;fill-rule:evenodd;stroke:#3c6eb4;stroke-width:1.05833328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> </g> </g> diff --git a/logo/podman-logo.png b/logo/podman-logo.png Binary files differindex b0dcc75d2..70c8b1191 100644 --- a/logo/podman-logo.png +++ b/logo/podman-logo.png diff --git a/pkg/hooks/docs/oci-hooks.5.md b/pkg/hooks/docs/oci-hooks.5.md index c876dd2f8..fc0442283 100644 --- a/pkg/hooks/docs/oci-hooks.5.md +++ b/pkg/hooks/docs/oci-hooks.5.md @@ -90,7 +90,7 @@ $ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json "path": "/usr/libexec/oci/hooks.d/oci-systemd-hook" } "when": { - "args": [".*/init$" , ".*/systemd$"], + "commands": [".*/init$" , ".*/systemd$"], }, "stages": ["prestart", "poststop"] } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 283585ef8..ed767f5ba 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -251,9 +251,11 @@ func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string] return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1]) } options = strings.Split(splitVol[1], ",") - if err := ValidateVolumeOpts(options); err != nil { + opts, err := ValidateVolumeOpts(options) + if err != nil { return nil, nil, err } + options = opts } ctr, err := runtime.LookupContainer(splitVol[0]) if err != nil { @@ -447,9 +449,11 @@ func getBindMount(args []string) (spec.Mount, error) { newMount.Source = newMount.Destination } - if err := ValidateVolumeOpts(newMount.Options); err != nil { + opts, err := ValidateVolumeOpts(newMount.Options) + if err != nil { return newMount, err } + newMount.Options = opts return newMount, nil } @@ -575,35 +579,45 @@ func ValidateVolumeCtrDir(ctrDir string) error { } // ValidateVolumeOpts validates a volume's options -func ValidateVolumeOpts(options []string) error { +func ValidateVolumeOpts(options []string) ([]string, error) { var foundRootPropagation, foundRWRO, foundLabelChange, bindType int + finalOpts := make([]string, 0, len(options)) for _, opt := range options { switch opt { case "rw", "ro": foundRWRO++ if foundRWRO > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", ")) + return nil, errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", ")) } case "z", "Z": foundLabelChange++ if foundLabelChange > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", ")) + return nil, errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", ")) } case "private", "rprivate", "shared", "rshared", "slave", "rslave": foundRootPropagation++ if foundRootPropagation > 1 { - return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", ")) + return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", ")) } case "bind", "rbind": bindType++ if bindType > 1 { - return errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", ")) - } + return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", ")) + } + case "cached", "delegated": + // The discarded ops are OS X specific volume options + // introduced in a recent Docker version. + // They have no meaning on Linux, so here we silently + // drop them. This matches Docker's behavior (the options + // are intended to be always safe to use, even not on OS + // X). + continue default: - return errors.Errorf("invalid option type %q", opt) + return nil, errors.Errorf("invalid mount option %q", opt) } + finalOpts = append(finalOpts, opt) } - return nil + return finalOpts, nil } // GetVolumeMounts takes user provided input for bind mounts and creates Mount structs @@ -633,9 +647,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string } if len(splitVol) > 2 { options = strings.Split(splitVol[2], ",") - if err := ValidateVolumeOpts(options); err != nil { + opts, err := ValidateVolumeOpts(options) + if err != nil { return nil, nil, err } + options = opts } if err := ValidateVolumeHostDir(src); err != nil { diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index 489e7eeef..40c99384d 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -20,26 +20,22 @@ func ProcessOptions(options []string) []string { foundbind, foundrw, foundro bool rootProp string ) + for _, opt := range options { switch opt { case "bind", "rbind": foundbind = true - break - } - } - if !foundbind { - options = append(options, "rbind") - } - for _, opt := range options { - switch opt { - case "rw": - foundrw = true case "ro": foundro = true + case "rw": + foundrw = true case "private", "rprivate", "slave", "rslave", "shared", "rshared": rootProp = opt } } + if !foundbind { + options = append(options, "rbind") + } if !foundrw && !foundro { options = append(options, "rw") } diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 9529346b4..8b6eab892 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -10,6 +10,7 @@ import ( "sort" "strings" "testing" + "time" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -22,6 +23,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" + "github.com/pkg/errors" ) var ( @@ -367,6 +369,18 @@ func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionInteg return session, session.ExitCode(), session.OutputToString() } +// RunNginxWithHealthCheck runs the alpine nginx container with an optional name and adds a healthcheck into it +func (p *PodmanTestIntegration) RunNginxWithHealthCheck(name string) (*PodmanSessionIntegration, string) { + var podmanArgs = []string{"run"} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-dt", "-P", "--healthcheck-command", "CMD-SHELL curl http://localhost/", nginx) + session := p.Podman(podmanArgs) + session.WaitWithDefaultTimeout() + return session, session.OutputToString() +} + func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { var podmanArgs = []string{"run", "--pod", pod} if name != "" { @@ -508,3 +522,16 @@ func (p *PodmanTestIntegration) ImageExistsInMainStore(idOrName string) bool { results.WaitWithDefaultTimeout() return Expect(results.ExitCode()).To(Equal(0)) } + +func (p *PodmanTestIntegration) RunHealthCheck(cid string) error { + for i := 0; i < 10; i++ { + hc := p.Podman([]string{"healthcheck", "run", cid}) + hc.WaitWithDefaultTimeout() + if hc.ExitCode() == 0 { + return nil + } + fmt.Printf("Waiting for %s to pass healthcheck\n", cid) + time.Sleep(1 * time.Second) + } + return errors.Errorf("unable to detect %s as running", cid) +} diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index e45118361..26c5fd7d0 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -48,11 +48,14 @@ var _ = Describe("Podman port", func() { Expect(result.ExitCode()).ToNot(Equal(0)) }) - It("podman port -l nginx", func() { - session := podmanTest.Podman([]string{"run", "-dt", "-P", nginx}) - session.WaitWithDefaultTimeout() + It("podman port -l nginx", func() { + session, cid := podmanTest.RunNginxWithHealthCheck("") Expect(session.ExitCode()).To(Equal(0)) + if err := podmanTest.RunHealthCheck(cid); err != nil { + Fail(err.Error()) + } + result := podmanTest.Podman([]string{"port", "-l"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -61,10 +64,13 @@ var _ = Describe("Podman port", func() { }) It("podman container port -l nginx", func() { - session := podmanTest.Podman([]string{"container", "run", "-dt", "-P", nginx}) - session.WaitWithDefaultTimeout() + session, cid := podmanTest.RunNginxWithHealthCheck("") Expect(session.ExitCode()).To(Equal(0)) + if err := podmanTest.RunHealthCheck(cid); err != nil { + Fail(err.Error()) + } + result := podmanTest.Podman([]string{"container", "port", "-l"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -73,10 +79,13 @@ var _ = Describe("Podman port", func() { }) It("podman port -l port nginx", func() { - session := podmanTest.Podman([]string{"run", "-dt", "-P", nginx}) - session.WaitWithDefaultTimeout() + session, cid := podmanTest.RunNginxWithHealthCheck("") Expect(session.ExitCode()).To(Equal(0)) + if err := podmanTest.RunHealthCheck(cid); err != nil { + Fail(err.Error()) + } + result := podmanTest.Podman([]string{"port", "-l", "80"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -85,10 +94,13 @@ var _ = Describe("Podman port", func() { }) It("podman port -a nginx", func() { - session := podmanTest.Podman([]string{"run", "-dt", "-P", nginx}) - session.WaitWithDefaultTimeout() + session, cid := podmanTest.RunNginxWithHealthCheck("") Expect(session.ExitCode()).To(Equal(0)) + if err := podmanTest.RunHealthCheck(cid); err != nil { + Fail(err.Error()) + } + result := podmanTest.Podman([]string{"port", "-a"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 3ba3c2bb3..3fc628589 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -712,28 +712,25 @@ USER mail` Expect(session.OutputToString()).To(Not(ContainSubstring("/dev/shm type tmpfs (ro,"))) }) - It("podman run with bad healthcheck interval", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-interval", "0.5s", ALPINE, "top"}) - session.Wait() - Expect(session.ExitCode()).ToNot(Equal(0)) - }) - It("podman run with bad healthcheck retries", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-retries", "0", ALPINE, "top"}) + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-command", "foo", "--healthcheck-retries", "0", ALPINE, "top"}) session.Wait() Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-retries must be greater than 0")) }) It("podman run with bad healthcheck timeout", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-timeout", "0s", ALPINE, "top"}) + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-command", "foo", "--healthcheck-timeout", "0s", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-timeout must be at least 1 second")) }) It("podman run with bad healthcheck start-period", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-start-period", "-1s", ALPINE, "top"}) + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-command", "foo", "--healthcheck-start-period", "-1s", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-start-period must be 0 seconds or greater")) }) It("podman run with --add-host and --no-hosts fails", func() { diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index d89c80909..9e160e73c 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -63,6 +63,24 @@ var _ = Describe("Podman run with volumes", func() { Expect(found).Should(BeTrue()) Expect(matches[0]).To(ContainSubstring("rw")) Expect(matches[0]).To(ContainSubstring("shared")) + + // Cached is ignored + session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:cached", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + found, matches = session.GrepString("/run/test") + Expect(found).Should(BeTrue()) + Expect(matches[0]).To(ContainSubstring("rw")) + Expect(matches[0]).To(Not(ContainSubstring("cached"))) + + // Delegated is ignored + session = podmanTest.Podman([]string{"run", "--rm", "-v", fmt.Sprintf("%s:/run/test:delegated", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + found, matches = session.GrepString("/run/test") + Expect(found).Should(BeTrue()) + Expect(matches[0]).To(ContainSubstring("rw")) + Expect(matches[0]).To(Not(ContainSubstring("delegated"))) }) It("podman run with --mount flag", func() { diff --git a/test/install/Dockerfile.Fedora b/test/install/Dockerfile.Fedora index 74cee771d..3e7b6e238 100644 --- a/test/install/Dockerfile.Fedora +++ b/test/install/Dockerfile.Fedora @@ -1,3 +1,3 @@ -FROM registry.fedoraproject.org/fedora:29 +FROM registry.fedoraproject.org/fedora:30 RUN dnf install -y rpms/x86_64/* diff --git a/test/system/030-run.bats b/test/system/030-run.bats index a29b1adc3..cefff0e2c 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -9,8 +9,8 @@ true | 0 | false | 1 | sh -c 'exit 32' | 32 | echo $rand | 0 | $rand -/no/such/command | 127 | Error: container create failed:.*exec:.* no such file or dir -/etc | 126 | Error: container create failed:.*exec:.* permission denied +/no/such/command | 127 | Error: .*: starting container process caused .*exec:.*stat /no/such/command: no such file or directory +/etc | 126 | Error: .*: starting container process caused .*exec:.* permission denied " while read cmd expected_rc expected_output; do diff --git a/test/test_podman_build.sh b/test/test_podman_build.sh index 39f1e784d..e3e53cae6 100644 --- a/test/test_podman_build.sh +++ b/test/test_podman_build.sh @@ -196,10 +196,22 @@ echo ######################################################## echo test "build with preprocessor" echo ######################################################## - target=alpine-image + TARGET=alpine-image podman build -q -t ${TARGET} -f Decomposed.in $HOME/test/build/preprocess buildah --debug=false images CID=$(buildah from $TARGET) buildah rm $CID podman rmi $(buildah --debug=false images -q) buildah --debug=false images -q + +echo ######################################################## +echo test "build with priv'd RUN" +echo ######################################################## + + TARGET=alpinepriv + podman build -q -t ${TARGET} -f $HOME/test/build/run-privd $HOME/test/build/run-privd + buildah --debug=false images + CID=$(buildah from $TARGET) + buildah rm $CID + podman rmi $(buildah --debug=false images -q) + buildah --debug=false images -q diff --git a/test/utils/utils.go b/test/utils/utils.go index 98031385d..43819350c 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -63,7 +63,7 @@ func (p *PodmanTest) MakeOptions(args []string) []string { return p.PodmanMakeOptions(args) } -// PodmanAsUserBase exec podman as user. uid and gid is set for credentials useage. env is used +// PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used // to record the env for debugging func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, nocache bool) *PodmanSession { var command *exec.Cmd diff --git a/vendor.conf b/vendor.conf index 9b9044b66..76e8b0003 100644 --- a/vendor.conf +++ b/vendor.conf @@ -49,6 +49,7 @@ github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-multierror v1.0.0 github.com/imdario/mergo v0.3.6 github.com/json-iterator/go 1.1.5 +github.com/konsorten/go-windows-terminal-sequences v1.0.2 github.com/modern-go/concurrent 1.0.3 github.com/modern-go/reflect2 v1.0.1 github.com/mistifyio/go-zfs v2.1.1 @@ -66,8 +67,7 @@ github.com/pmezard/go-difflib v1.0.0 github.com/pquerna/ffjson e517b90714f7c0eabe6d2e570a5886ae077d6db6 github.com/seccomp/libseccomp-golang v0.9.0 github.com/seccomp/containers-golang v0.1 -# TODO: logrus.IsTerminal() is private starting with v1.1.x and requires code changes in libpod -github.com/sirupsen/logrus v1.0.0 +github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.3.0 github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2 @@ -93,7 +93,7 @@ k8s.io/api kubernetes-1.10.13-beta.0 https://github.com/kubernetes/api k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apimachinery k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 -github.com/containers/buildah v1.8.4 +github.com/containers/buildah v1.9.0 github.com/varlink/go 0f1d566d194b9d6d48e0d47c5e4d822628919066 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index b97e048cc..329835d7a 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.4" + Version = "1.9.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 diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index 55f9502b2..0bf37da59 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -1134,8 +1134,14 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy setNonblock(wfd, writeDesc[wfd], false) } - setNonblock(stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) + if copyPipes { + setNonblock(stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) + } + + runCopyStdioPassData(stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec, relayMap, relayBuffer, readDesc, writeDesc) +} +func runCopyStdioPassData(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec, relayMap map[int]int, relayBuffer map[int]*bytes.Buffer, readDesc map[int]string, writeDesc map[int]string) { closeStdin := false // Pass data back and forth. diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE b/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE new file mode 100644 index 000000000..14127cd83 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE @@ -0,0 +1,9 @@ +(The MIT License) + +Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md new file mode 100644 index 000000000..195333e51 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md @@ -0,0 +1,41 @@ +# Windows Terminal Sequences + +This library allow for enabling Windows terminal color support for Go. + +See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details. + +## Usage + +```go +import ( + "syscall" + + sequences "github.com/konsorten/go-windows-terminal-sequences" +) + +func main() { + sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true) +} + +``` + +## Authors + +The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de). + +We thank all the authors who provided code to this library: + +* Felix Kollmann +* Nicolas Perraut + +## License + +(The MIT License) + +Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod b/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod new file mode 100644 index 000000000..716c61312 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod @@ -0,0 +1 @@ +module github.com/konsorten/go-windows-terminal-sequences diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go new file mode 100644 index 000000000..ef18d8f97 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go @@ -0,0 +1,36 @@ +// +build windows + +package sequences + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll") + setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode") +) + +func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error { + const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4 + + var mode uint32 + err := syscall.GetConsoleMode(syscall.Stdout, &mode) + if err != nil { + return err + } + + if enable { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING + } else { + mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING + } + + ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode)) + if ret == 0 { + return err + } + + return nil +} diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go new file mode 100644 index 000000000..df61a6f2f --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go @@ -0,0 +1,11 @@ +// +build linux darwin + +package sequences + +import ( + "fmt" +) + +func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error { + return fmt.Errorf("windows only package") +} diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md index cbe8b6962..a4796eb07 100644 --- a/vendor/github.com/sirupsen/logrus/README.md +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -1,22 +1,24 @@ # Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus) Logrus is a structured logger for Go (golang), completely API compatible with -the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not -yet stable (pre 1.0). Logrus itself is completely stable and has been used in -many large deployments. The core API is unlikely to change much but please -version control your Logrus to make sure you aren't fetching latest `master` on -every build.** - -**Seeing weird case-sensitive problems?** Unfortunately, the author failed to -realize the consequences of renaming to lower-case. Due to the Go package -environment, this caused issues. Regretfully, there's no turning back now. +the standard library logger. + +**Seeing weird case-sensitive problems?** It's in the past been possible to +import Logrus as both upper- and lower-case. Due to the Go package environment, +this caused issues in the community and we needed a standard. Some environments +experienced problems with the upper-case variant, so the lower-case was decided. Everything using `logrus` will need to use the lower-case: `github.com/sirupsen/logrus`. Any package that isn't, should be changed. -I am terribly sorry for this inconvenience. Logrus strives hard for backwards -compatibility, and the author failed to realize the cascading consequences of -such a name-change. To fix Glide, see [these +To fix Glide, see [these comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437). +For an in-depth explanation of the casing issue, see [this +comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276). + +**Are you interested in assisting in maintaining Logrus?** Currently I have a +lot of obligations, and I am unable to provide Logrus with the maintainership it +needs. If you'd like to help, please reach out to me at `simon at author's +username dot com`. Nicely color-coded in development (when a TTY is attached, otherwise just plain text): @@ -54,9 +56,40 @@ time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true -exit status 1 +``` +To ensure this behaviour even if a TTY is attached, set your formatter as follows: + +```go + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) ``` +#### Logging Method Name + +If you wish to add the calling method as a field, instruct the logger via: +```go +log.SetReportCaller(true) +``` +This adds the caller as 'method' like so: + +```json +{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", +"time":"2014-03-10 19:57:38.562543129 -0400 EDT"} +``` + +```text +time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin +``` +Note that this does add measurable overhead - the cost will depend on the version of Go, but is +between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your +environment via benchmarks: +``` +go test -bench=.*CallerTracing +``` + + #### Case-sensitivity The organization's name was changed to lower-case--and this will not be changed @@ -218,7 +251,7 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in ```go import ( log "github.com/sirupsen/logrus" - "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake" + "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" ) @@ -239,59 +272,15 @@ func init() { ``` Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). -| Hook | Description | -| ----- | ----------- | -| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | -| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. | -| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) | -| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) | -| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | -| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic | -| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) | -| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch| -| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/) -| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd | -| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) | -| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) | -| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | -| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | -| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb | -| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) | -| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | -| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka | -| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | -| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) | -| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) | -| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) | -| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | -| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) | -| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | -| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | -| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) | -| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit | -| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. | -| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) | -| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) | -| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | -| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) | -| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | -| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)| -| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. | -| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | -| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) | -| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)| -| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | -| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. | -| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) | -| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) | -| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash | -| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) | +A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) + #### Level logging -Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. +Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. ```go +log.Trace("Something very low level.") log.Debug("Useful debugging information.") log.Info("Something noteworthy happened!") log.Warn("You should probably take a look at this.") @@ -363,15 +352,20 @@ The built-in logging formatters are: field to `true`. To force no colored output even if there is a TTY set the `DisableColors` field to `true`. For Windows, see [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable). + * When colors are enabled, levels are truncated to 4 characters by default. To disable + truncation set the `DisableLevelTruncation` field to `true`. * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). * `logrus.JSONFormatter`. Logs fields as JSON. * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). Third party logging formatters: +* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. +* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). * [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events. * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. +* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. You can define your formatter by implementing the `Formatter` interface, requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a @@ -449,13 +443,13 @@ Logrus has a built in facility for asserting the presence of log messages. This ```go import( "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/null" + "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "testing" ) func TestSomething(t*testing.T){ - logger, hook := null.NewNullLogger() + logger, hook := test.NewNullLogger() logger.Error("Helloerror") assert.Equal(t, 1, len(hook.Entries)) @@ -485,7 +479,7 @@ logrus.RegisterExitHandler(handler) #### Thread safety -By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs. +By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. Situation when locking is not needed includes: diff --git a/vendor/github.com/sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go index 8af90637a..8fd189e1c 100644 --- a/vendor/github.com/sirupsen/logrus/alt_exit.go +++ b/vendor/github.com/sirupsen/logrus/alt_exit.go @@ -51,9 +51,9 @@ func Exit(code int) { os.Exit(code) } -// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke -// all handlers. The handlers will also be invoked when any Fatal log entry is -// made. +// RegisterExitHandler appends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. // // This method is useful when a caller wishes to use logrus to log a fatal // message but also needs to gracefully shutdown. An example usecase could be @@ -62,3 +62,15 @@ func Exit(code int) { func RegisterExitHandler(handler func()) { handlers = append(handlers, handler) } + +// DeferExitHandler prepends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func DeferExitHandler(handler func()) { + handlers = append([]func(){handler}, handlers...) +} diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go index 320e5d5b8..63e25583c 100644 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -2,13 +2,33 @@ package logrus import ( "bytes" + "context" "fmt" "os" + "reflect" + "runtime" + "strings" "sync" "time" ) -var bufferPool *sync.Pool +var ( + bufferPool *sync.Pool + + // qualified package name, cached at first use + logrusPackage string + + // Positions in the call stack when tracing to report the calling method + minimumCallerDepth int + + // Used for caller information initialisation + callerInitOnce sync.Once +) + +const ( + maximumCallerDepth int = 25 + knownLogrusFrames int = 4 +) func init() { bufferPool = &sync.Pool{ @@ -16,15 +36,18 @@ func init() { return new(bytes.Buffer) }, } + + // start at the bottom of the stack before the package-name cache is primed + minimumCallerDepth = 1 } // Defines the key when adding errors using WithError. var ErrorKey = "error" // An entry is the final or intermediate Logrus logging entry. It contains all -// the fields passed with WithField{,s}. It's finally logged when Debug, Info, -// Warn, Error, Fatal or Panic is called on it. These objects can be reused and -// passed around as much as you wish to avoid field duplication. +// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, +// Info, Warn, Error, Fatal or Panic is called on it. These objects can be +// reused and passed around as much as you wish to avoid field duplication. type Entry struct { Logger *Logger @@ -34,21 +57,31 @@ type Entry struct { // Time at which the log entry was created Time time.Time - // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic + // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic + // This field will be set on entry firing and the value will be equal to the one in Logger struct field. Level Level - // Message passed to Debug, Info, Warn, Error, Fatal or Panic + // Calling method, with package name + Caller *runtime.Frame + + // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic Message string - // When formatter is called in entry.log(), an Buffer may be set to entry + // When formatter is called in entry.log(), a Buffer may be set to entry Buffer *bytes.Buffer + + // Contains the context set by the user. Useful for hook processing etc. + Context context.Context + + // err may contain a field formatting error + err string } func NewEntry(logger *Logger) *Entry { return &Entry{ Logger: logger, - // Default is three fields, give a little extra room - Data: make(Fields, 5), + // Default is three fields, plus one optional. Give a little extra room. + Data: make(Fields, 6), } } @@ -68,6 +101,11 @@ func (entry *Entry) WithError(err error) *Entry { return entry.WithField(ErrorKey, err) } +// Add a context to the Entry. +func (entry *Entry) WithContext(ctx context.Context) *Entry { + return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx} +} + // Add a single field to the Entry. func (entry *Entry) WithField(key string, value interface{}) *Entry { return entry.WithFields(Fields{key: value}) @@ -79,56 +117,164 @@ func (entry *Entry) WithFields(fields Fields) *Entry { for k, v := range entry.Data { data[k] = v } + fieldErr := entry.err for k, v := range fields { - data[k] = v + isErrField := false + if t := reflect.TypeOf(v); t != nil { + switch t.Kind() { + case reflect.Func: + isErrField = true + case reflect.Ptr: + isErrField = t.Elem().Kind() == reflect.Func + } + } + if isErrField { + tmp := fmt.Sprintf("can not add field %q", k) + if fieldErr != "" { + fieldErr = entry.err + ", " + tmp + } else { + fieldErr = tmp + } + } else { + data[k] = v + } + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} +} + +// Overrides the time of the Entry. +func (entry *Entry) WithTime(t time.Time) *Entry { + return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context} +} + +// getPackageName reduces a fully qualified function name to the package name +// There really ought to be to be a better way... +func getPackageName(f string) string { + for { + lastPeriod := strings.LastIndex(f, ".") + lastSlash := strings.LastIndex(f, "/") + if lastPeriod > lastSlash { + f = f[:lastPeriod] + } else { + break + } } - return &Entry{Logger: entry.Logger, Data: data} + + return f +} + +// getCaller retrieves the name of the first non-logrus calling function +func getCaller() *runtime.Frame { + + // cache this package's fully-qualified name + callerInitOnce.Do(func() { + pcs := make([]uintptr, 2) + _ = runtime.Callers(0, pcs) + logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name()) + + // now that we have the cache, we can skip a minimum count of known-logrus functions + // XXX this is dubious, the number of frames may vary + minimumCallerDepth = knownLogrusFrames + }) + + // Restrict the lookback frames to avoid runaway lookups + pcs := make([]uintptr, maximumCallerDepth) + depth := runtime.Callers(minimumCallerDepth, pcs) + frames := runtime.CallersFrames(pcs[:depth]) + + for f, again := frames.Next(); again; f, again = frames.Next() { + pkg := getPackageName(f.Function) + + // If the caller isn't part of this package, we're done + if pkg != logrusPackage { + return &f + } + } + + // if we got here, we failed to find the caller's context + return nil +} + +func (entry Entry) HasCaller() (has bool) { + return entry.Logger != nil && + entry.Logger.ReportCaller && + entry.Caller != nil } // This function is not declared with a pointer value because otherwise // race conditions will occur when using multiple goroutines func (entry Entry) log(level Level, msg string) { var buffer *bytes.Buffer - entry.Time = time.Now() + + // Default to now, but allow users to override if they want. + // + // We don't have to worry about polluting future calls to Entry#log() + // with this assignment because this function is declared with a + // non-pointer receiver. + if entry.Time.IsZero() { + entry.Time = time.Now() + } + entry.Level = level entry.Message = msg - - if err := entry.Logger.Hooks.Fire(level, &entry); err != nil { - entry.Logger.mu.Lock() - fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) - entry.Logger.mu.Unlock() + if entry.Logger.ReportCaller { + entry.Caller = getCaller() } + + entry.fireHooks() + buffer = bufferPool.Get().(*bytes.Buffer) buffer.Reset() defer bufferPool.Put(buffer) entry.Buffer = buffer - serialized, err := entry.Logger.Formatter.Format(&entry) + + entry.write() + entry.Buffer = nil + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(&entry) + } +} + +func (entry *Entry) fireHooks() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + err := entry.Logger.Hooks.Fire(entry.Level, entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + } +} + +func (entry *Entry) write() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + serialized, err := entry.Logger.Formatter.Format(entry) if err != nil { - entry.Logger.mu.Lock() fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) - entry.Logger.mu.Unlock() } else { - entry.Logger.mu.Lock() _, err = entry.Logger.Out.Write(serialized) if err != nil { fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) } - entry.Logger.mu.Unlock() } +} - // To avoid Entry#log() returning a value that only would make sense for - // panic() to use in Entry#Panic(), we avoid the allocation by checking - // directly here. - if level <= PanicLevel { - panic(&entry) +func (entry *Entry) Log(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.log(level, fmt.Sprint(args...)) } } +func (entry *Entry) Trace(args ...interface{}) { + entry.Log(TraceLevel, args...) +} + func (entry *Entry) Debug(args ...interface{}) { - if entry.Logger.level() >= DebugLevel { - entry.log(DebugLevel, fmt.Sprint(args...)) - } + entry.Log(DebugLevel, args...) } func (entry *Entry) Print(args ...interface{}) { @@ -136,15 +282,11 @@ func (entry *Entry) Print(args ...interface{}) { } func (entry *Entry) Info(args ...interface{}) { - if entry.Logger.level() >= InfoLevel { - entry.log(InfoLevel, fmt.Sprint(args...)) - } + entry.Log(InfoLevel, args...) } func (entry *Entry) Warn(args ...interface{}) { - if entry.Logger.level() >= WarnLevel { - entry.log(WarnLevel, fmt.Sprint(args...)) - } + entry.Log(WarnLevel, args...) } func (entry *Entry) Warning(args ...interface{}) { @@ -152,37 +294,37 @@ func (entry *Entry) Warning(args ...interface{}) { } func (entry *Entry) Error(args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { - entry.log(ErrorLevel, fmt.Sprint(args...)) - } + entry.Log(ErrorLevel, args...) } func (entry *Entry) Fatal(args ...interface{}) { - if entry.Logger.level() >= FatalLevel { - entry.log(FatalLevel, fmt.Sprint(args...)) - } - Exit(1) + entry.Log(FatalLevel, args...) + entry.Logger.Exit(1) } func (entry *Entry) Panic(args ...interface{}) { - if entry.Logger.level() >= PanicLevel { - entry.log(PanicLevel, fmt.Sprint(args...)) - } + entry.Log(PanicLevel, args...) panic(fmt.Sprint(args...)) } // Entry Printf family functions -func (entry *Entry) Debugf(format string, args ...interface{}) { - if entry.Logger.level() >= DebugLevel { - entry.Debug(fmt.Sprintf(format, args...)) +func (entry *Entry) Logf(level Level, format string, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, fmt.Sprintf(format, args...)) } } +func (entry *Entry) Tracef(format string, args ...interface{}) { + entry.Logf(TraceLevel, format, args...) +} + +func (entry *Entry) Debugf(format string, args ...interface{}) { + entry.Logf(DebugLevel, format, args...) +} + func (entry *Entry) Infof(format string, args ...interface{}) { - if entry.Logger.level() >= InfoLevel { - entry.Info(fmt.Sprintf(format, args...)) - } + entry.Logf(InfoLevel, format, args...) } func (entry *Entry) Printf(format string, args ...interface{}) { @@ -190,9 +332,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) { } func (entry *Entry) Warnf(format string, args ...interface{}) { - if entry.Logger.level() >= WarnLevel { - entry.Warn(fmt.Sprintf(format, args...)) - } + entry.Logf(WarnLevel, format, args...) } func (entry *Entry) Warningf(format string, args ...interface{}) { @@ -200,36 +340,36 @@ func (entry *Entry) Warningf(format string, args ...interface{}) { } func (entry *Entry) Errorf(format string, args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { - entry.Error(fmt.Sprintf(format, args...)) - } + entry.Logf(ErrorLevel, format, args...) } func (entry *Entry) Fatalf(format string, args ...interface{}) { - if entry.Logger.level() >= FatalLevel { - entry.Fatal(fmt.Sprintf(format, args...)) - } - Exit(1) + entry.Logf(FatalLevel, format, args...) + entry.Logger.Exit(1) } func (entry *Entry) Panicf(format string, args ...interface{}) { - if entry.Logger.level() >= PanicLevel { - entry.Panic(fmt.Sprintf(format, args...)) - } + entry.Logf(PanicLevel, format, args...) } // Entry Println family functions -func (entry *Entry) Debugln(args ...interface{}) { - if entry.Logger.level() >= DebugLevel { - entry.Debug(entry.sprintlnn(args...)) +func (entry *Entry) Logln(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, entry.sprintlnn(args...)) } } +func (entry *Entry) Traceln(args ...interface{}) { + entry.Logln(TraceLevel, args...) +} + +func (entry *Entry) Debugln(args ...interface{}) { + entry.Logln(DebugLevel, args...) +} + func (entry *Entry) Infoln(args ...interface{}) { - if entry.Logger.level() >= InfoLevel { - entry.Info(entry.sprintlnn(args...)) - } + entry.Logln(InfoLevel, args...) } func (entry *Entry) Println(args ...interface{}) { @@ -237,9 +377,7 @@ func (entry *Entry) Println(args ...interface{}) { } func (entry *Entry) Warnln(args ...interface{}) { - if entry.Logger.level() >= WarnLevel { - entry.Warn(entry.sprintlnn(args...)) - } + entry.Logln(WarnLevel, args...) } func (entry *Entry) Warningln(args ...interface{}) { @@ -247,22 +385,16 @@ func (entry *Entry) Warningln(args ...interface{}) { } func (entry *Entry) Errorln(args ...interface{}) { - if entry.Logger.level() >= ErrorLevel { - entry.Error(entry.sprintlnn(args...)) - } + entry.Logln(ErrorLevel, args...) } func (entry *Entry) Fatalln(args ...interface{}) { - if entry.Logger.level() >= FatalLevel { - entry.Fatal(entry.sprintlnn(args...)) - } - Exit(1) + entry.Logln(FatalLevel, args...) + entry.Logger.Exit(1) } func (entry *Entry) Panicln(args ...interface{}) { - if entry.Logger.level() >= PanicLevel { - entry.Panic(entry.sprintlnn(args...)) - } + entry.Logln(PanicLevel, args...) } // Sprintlnn => Sprint no newline. This is to get the behavior of how diff --git a/vendor/github.com/sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go index 1aeaa90ba..62fc2f219 100644 --- a/vendor/github.com/sirupsen/logrus/exported.go +++ b/vendor/github.com/sirupsen/logrus/exported.go @@ -1,7 +1,9 @@ package logrus import ( + "context" "io" + "time" ) var ( @@ -15,37 +17,38 @@ func StandardLogger() *Logger { // SetOutput sets the standard logger output. func SetOutput(out io.Writer) { - std.mu.Lock() - defer std.mu.Unlock() - std.Out = out + std.SetOutput(out) } // SetFormatter sets the standard logger formatter. func SetFormatter(formatter Formatter) { - std.mu.Lock() - defer std.mu.Unlock() - std.Formatter = formatter + std.SetFormatter(formatter) +} + +// SetReportCaller sets whether the standard logger will include the calling +// method as a field. +func SetReportCaller(include bool) { + std.SetReportCaller(include) } // SetLevel sets the standard logger level. func SetLevel(level Level) { - std.mu.Lock() - defer std.mu.Unlock() - std.setLevel(level) + std.SetLevel(level) } // GetLevel returns the standard logger level. func GetLevel() Level { - std.mu.Lock() - defer std.mu.Unlock() - return std.level() + return std.GetLevel() +} + +// IsLevelEnabled checks if the log level of the standard logger is greater than the level param +func IsLevelEnabled(level Level) bool { + return std.IsLevelEnabled(level) } // AddHook adds a hook to the standard logger hooks. func AddHook(hook Hook) { - std.mu.Lock() - defer std.mu.Unlock() - std.Hooks.Add(hook) + std.AddHook(hook) } // WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. @@ -53,6 +56,11 @@ func WithError(err error) *Entry { return std.WithField(ErrorKey, err) } +// WithContext creates an entry from the standard logger and adds a context to it. +func WithContext(ctx context.Context) *Entry { + return std.WithContext(ctx) +} + // WithField creates an entry from the standard logger and adds a field to // it. If you want multiple fields, use `WithFields`. // @@ -72,6 +80,20 @@ func WithFields(fields Fields) *Entry { return std.WithFields(fields) } +// WithTime creats an entry from the standard logger and overrides the time of +// logs generated with it. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithTime(t time.Time) *Entry { + return std.WithTime(t) +} + +// Trace logs a message at level Trace on the standard logger. +func Trace(args ...interface{}) { + std.Trace(args...) +} + // Debug logs a message at level Debug on the standard logger. func Debug(args ...interface{}) { std.Debug(args...) @@ -107,11 +129,16 @@ func Panic(args ...interface{}) { std.Panic(args...) } -// Fatal logs a message at level Fatal on the standard logger. +// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatal(args ...interface{}) { std.Fatal(args...) } +// Tracef logs a message at level Trace on the standard logger. +func Tracef(format string, args ...interface{}) { + std.Tracef(format, args...) +} + // Debugf logs a message at level Debug on the standard logger. func Debugf(format string, args ...interface{}) { std.Debugf(format, args...) @@ -147,11 +174,16 @@ func Panicf(format string, args ...interface{}) { std.Panicf(format, args...) } -// Fatalf logs a message at level Fatal on the standard logger. +// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...) } +// Traceln logs a message at level Trace on the standard logger. +func Traceln(args ...interface{}) { + std.Traceln(args...) +} + // Debugln logs a message at level Debug on the standard logger. func Debugln(args ...interface{}) { std.Debugln(args...) @@ -187,7 +219,7 @@ func Panicln(args ...interface{}) { std.Panicln(args...) } -// Fatalln logs a message at level Fatal on the standard logger. +// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. func Fatalln(args ...interface{}) { std.Fatalln(args...) } diff --git a/vendor/github.com/sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go index b5fbe934d..408883773 100644 --- a/vendor/github.com/sirupsen/logrus/formatter.go +++ b/vendor/github.com/sirupsen/logrus/formatter.go @@ -2,7 +2,16 @@ package logrus import "time" -const DefaultTimestampFormat = time.RFC3339 +// Default key names for the default fields +const ( + defaultTimestampFormat = time.RFC3339 + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyLogrusError = "logrus_error" + FieldKeyFunc = "func" + FieldKeyFile = "file" +) // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: @@ -18,7 +27,7 @@ type Formatter interface { Format(*Entry) ([]byte, error) } -// This is to not silently overwrite `time`, `msg` and `level` fields when +// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when // dumping it. If this code wasn't there doing: // // logrus.WithField("level", 1).Info("hello") @@ -30,16 +39,40 @@ type Formatter interface { // // It's not exported because it's still using Data in an opinionated way. It's to // avoid code duplication between the two default formatters. -func prefixFieldClashes(data Fields) { - if t, ok := data["time"]; ok { - data["fields.time"] = t +func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) { + timeKey := fieldMap.resolve(FieldKeyTime) + if t, ok := data[timeKey]; ok { + data["fields."+timeKey] = t + delete(data, timeKey) } - if m, ok := data["msg"]; ok { - data["fields.msg"] = m + msgKey := fieldMap.resolve(FieldKeyMsg) + if m, ok := data[msgKey]; ok { + data["fields."+msgKey] = m + delete(data, msgKey) } - if l, ok := data["level"]; ok { - data["fields.level"] = l + levelKey := fieldMap.resolve(FieldKeyLevel) + if l, ok := data[levelKey]; ok { + data["fields."+levelKey] = l + delete(data, levelKey) + } + + logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) + if l, ok := data[logrusErrKey]; ok { + data["fields."+logrusErrKey] = l + delete(data, logrusErrKey) + } + + // If reportCaller is not set, 'func' will not conflict. + if reportCaller { + funcKey := fieldMap.resolve(FieldKeyFunc) + if l, ok := data[funcKey]; ok { + data["fields."+funcKey] = l + } + fileKey := fieldMap.resolve(FieldKeyFile) + if l, ok := data[fileKey]; ok { + data["fields."+fileKey] = l + } } } diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod new file mode 100644 index 000000000..12fdf9898 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/go.mod @@ -0,0 +1,10 @@ +module github.com/sirupsen/logrus + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.1 // indirect + github.com/stretchr/testify v1.2.2 + golang.org/x/sys v0.0.0-20190422165155-953cdadca894 +) diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md index 92b391c17..1bbc0f72d 100644 --- a/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md +++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/README.md @@ -6,12 +6,12 @@ import ( "log/syslog" "github.com/sirupsen/logrus" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" ) func main() { log := logrus.New() - hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err == nil { log.Hooks.Add(hook) @@ -25,12 +25,12 @@ If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or " import ( "log/syslog" "github.com/sirupsen/logrus" - logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" + lSyslog "github.com/sirupsen/logrus/hooks/syslog" ) func main() { log := logrus.New() - hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "") + hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") if err == nil { log.Hooks.Add(hook) diff --git a/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go index 204f0016d..02b8df380 100644 --- a/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go +++ b/vendor/github.com/sirupsen/logrus/hooks/syslog/syslog.go @@ -1,12 +1,13 @@ // +build !windows,!nacl,!plan9 -package logrus_syslog +package syslog import ( "fmt" - "github.com/sirupsen/logrus" "log/syslog" "os" + + "github.com/sirupsen/logrus" ) // SyslogHook to send logs via syslog. @@ -42,7 +43,7 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error { return hook.Writer.Warning(line) case logrus.InfoLevel: return hook.Writer.Info(line) - case logrus.DebugLevel: + case logrus.DebugLevel, logrus.TraceLevel: return hook.Writer.Debug(line) default: return nil diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go index e787ea175..098a21a06 100644 --- a/vendor/github.com/sirupsen/logrus/json_formatter.go +++ b/vendor/github.com/sirupsen/logrus/json_formatter.go @@ -1,18 +1,16 @@ package logrus import ( + "bytes" "encoding/json" "fmt" + "runtime" ) type fieldKey string -type FieldMap map[fieldKey]string -const ( - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" -) +// FieldMap allows customization of the key names for default fields. +type FieldMap map[fieldKey]string func (f FieldMap) resolve(key fieldKey) string { if k, ok := f[key]; ok { @@ -22,6 +20,7 @@ func (f FieldMap) resolve(key fieldKey) string { return string(key) } +// JSONFormatter formats logs into parsable json type JSONFormatter struct { // TimestampFormat sets the format used for marshaling timestamps. TimestampFormat string @@ -29,20 +28,34 @@ type JSONFormatter struct { // DisableTimestamp allows disabling automatic timestamps in output DisableTimestamp bool - // FieldMap allows users to customize the names of keys for various fields. + // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. + DataKey string + + // FieldMap allows users to customize the names of keys for default fields. // As an example: // formatter := &JSONFormatter{ // FieldMap: FieldMap{ - // FieldKeyTime: "@timestamp", + // FieldKeyTime: "@timestamp", // FieldKeyLevel: "@level", - // FieldKeyMsg: "@message", + // FieldKeyMsg: "@message", + // FieldKeyFunc: "@caller", // }, // } FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the json data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from json fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + // PrettyPrint will indent all json logs + PrettyPrint bool } +// Format renders a single log entry func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { - data := make(Fields, len(entry.Data)+3) + data := make(Fields, len(entry.Data)+4) for k, v := range entry.Data { switch v := v.(type) { case error: @@ -53,22 +66,56 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data[k] = v } } - prefixFieldClashes(data) + + if f.DataKey != "" { + newData := make(Fields, 4) + newData[f.DataKey] = data + data = newData + } + + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) timestampFormat := f.TimestampFormat if timestampFormat == "" { - timestampFormat = DefaultTimestampFormat + timestampFormat = defaultTimestampFormat } + if entry.err != "" { + data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err + } if !f.DisableTimestamp { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) } data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() + if entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + if funcVal != "" { + data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal + } + if fileVal != "" { + data[f.FieldMap.resolve(FieldKeyFile)] = fileVal + } + } - serialized, err := json.Marshal(data) - if err != nil { - return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} } - return append(serialized, '\n'), nil + + encoder := json.NewEncoder(b) + if f.PrettyPrint { + encoder.SetIndent("", " ") + } + if err := encoder.Encode(data); err != nil { + return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err) + } + + return b.Bytes(), nil } diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go index 370fff5d1..c0c0b1e55 100644 --- a/vendor/github.com/sirupsen/logrus/logger.go +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -1,16 +1,18 @@ package logrus import ( + "context" "io" "os" "sync" "sync/atomic" + "time" ) type Logger struct { // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a // file, or leave it default which is `os.Stderr`. You can also set this to - // something more adventorous, such as logging to Kafka. + // something more adventurous, such as logging to Kafka. Out io.Writer // Hooks for the logger instance. These allow firing events based on logging // levels and log entries. For example, to send errors to an error tracking @@ -23,16 +25,24 @@ type Logger struct { // own that implements the `Formatter` interface, see the `README` or included // formatters for examples. Formatter Formatter + + // Flag for whether to log caller info (off by default) + ReportCaller bool + // The logging level the logger should log at. This is typically (and defaults // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be - // logged. `logrus.Debug` is useful in + // logged. Level Level // Used to sync writing to the log. Locking is enabled by Default mu MutexWrap // Reusable empty entry entryPool sync.Pool + // Function to exit the application, defaults to `os.Exit()` + ExitFunc exitFunc } +type exitFunc func(int) + type MutexWrap struct { lock sync.Mutex disabled bool @@ -68,10 +78,12 @@ func (mw *MutexWrap) Disable() { // It's recommended to make this a global instance called `log`. func New() *Logger { return &Logger{ - Out: os.Stderr, - Formatter: new(TextFormatter), - Hooks: make(LevelHooks), - Level: InfoLevel, + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + ExitFunc: os.Exit, + ReportCaller: false, } } @@ -84,11 +96,12 @@ func (logger *Logger) newEntry() *Entry { } func (logger *Logger) releaseEntry(entry *Entry) { + entry.Data = map[string]interface{}{} logger.entryPool.Put(entry) } // Adds a field to the log entry, note that it doesn't log until you call -// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. +// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry. // If you want multiple fields, use `WithFields`. func (logger *Logger) WithField(key string, value interface{}) *Entry { entry := logger.newEntry() @@ -112,20 +125,38 @@ func (logger *Logger) WithError(err error) *Entry { return entry.WithError(err) } -func (logger *Logger) Debugf(format string, args ...interface{}) { - if logger.level() >= DebugLevel { +// Add a context to the log entry. +func (logger *Logger) WithContext(ctx context.Context) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithContext(ctx) +} + +// Overrides the time of the log entry. +func (logger *Logger) WithTime(t time.Time) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithTime(t) +} + +func (logger *Logger) Logf(level Level, format string, args ...interface{}) { + if logger.IsLevelEnabled(level) { entry := logger.newEntry() - entry.Debugf(format, args...) + entry.Logf(level, format, args...) logger.releaseEntry(entry) } } +func (logger *Logger) Tracef(format string, args ...interface{}) { + logger.Logf(TraceLevel, format, args...) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + logger.Logf(DebugLevel, format, args...) +} + func (logger *Logger) Infof(format string, args ...interface{}) { - if logger.level() >= InfoLevel { - entry := logger.newEntry() - entry.Infof(format, args...) - logger.releaseEntry(entry) - } + logger.Logf(InfoLevel, format, args...) } func (logger *Logger) Printf(format string, args ...interface{}) { @@ -135,123 +166,91 @@ func (logger *Logger) Printf(format string, args ...interface{}) { } func (logger *Logger) Warnf(format string, args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warnf(format, args...) - logger.releaseEntry(entry) - } + logger.Logf(WarnLevel, format, args...) } func (logger *Logger) Warningf(format string, args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warnf(format, args...) - logger.releaseEntry(entry) - } + logger.Warnf(format, args...) } func (logger *Logger) Errorf(format string, args ...interface{}) { - if logger.level() >= ErrorLevel { - entry := logger.newEntry() - entry.Errorf(format, args...) - logger.releaseEntry(entry) - } + logger.Logf(ErrorLevel, format, args...) } func (logger *Logger) Fatalf(format string, args ...interface{}) { - if logger.level() >= FatalLevel { - entry := logger.newEntry() - entry.Fatalf(format, args...) - logger.releaseEntry(entry) - } - Exit(1) + logger.Logf(FatalLevel, format, args...) + logger.Exit(1) } func (logger *Logger) Panicf(format string, args ...interface{}) { - if logger.level() >= PanicLevel { + logger.Logf(PanicLevel, format, args...) +} + +func (logger *Logger) Log(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { entry := logger.newEntry() - entry.Panicf(format, args...) + entry.Log(level, args...) logger.releaseEntry(entry) } } +func (logger *Logger) Trace(args ...interface{}) { + logger.Log(TraceLevel, args...) +} + func (logger *Logger) Debug(args ...interface{}) { - if logger.level() >= DebugLevel { - entry := logger.newEntry() - entry.Debug(args...) - logger.releaseEntry(entry) - } + logger.Log(DebugLevel, args...) } func (logger *Logger) Info(args ...interface{}) { - if logger.level() >= InfoLevel { - entry := logger.newEntry() - entry.Info(args...) - logger.releaseEntry(entry) - } + logger.Log(InfoLevel, args...) } func (logger *Logger) Print(args ...interface{}) { entry := logger.newEntry() - entry.Info(args...) + entry.Print(args...) logger.releaseEntry(entry) } func (logger *Logger) Warn(args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warn(args...) - logger.releaseEntry(entry) - } + logger.Log(WarnLevel, args...) } func (logger *Logger) Warning(args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warn(args...) - logger.releaseEntry(entry) - } + logger.Warn(args...) } func (logger *Logger) Error(args ...interface{}) { - if logger.level() >= ErrorLevel { - entry := logger.newEntry() - entry.Error(args...) - logger.releaseEntry(entry) - } + logger.Log(ErrorLevel, args...) } func (logger *Logger) Fatal(args ...interface{}) { - if logger.level() >= FatalLevel { - entry := logger.newEntry() - entry.Fatal(args...) - logger.releaseEntry(entry) - } - Exit(1) + logger.Log(FatalLevel, args...) + logger.Exit(1) } func (logger *Logger) Panic(args ...interface{}) { - if logger.level() >= PanicLevel { + logger.Log(PanicLevel, args...) +} + +func (logger *Logger) Logln(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { entry := logger.newEntry() - entry.Panic(args...) + entry.Logln(level, args...) logger.releaseEntry(entry) } } +func (logger *Logger) Traceln(args ...interface{}) { + logger.Logln(TraceLevel, args...) +} + func (logger *Logger) Debugln(args ...interface{}) { - if logger.level() >= DebugLevel { - entry := logger.newEntry() - entry.Debugln(args...) - logger.releaseEntry(entry) - } + logger.Logln(DebugLevel, args...) } func (logger *Logger) Infoln(args ...interface{}) { - if logger.level() >= InfoLevel { - entry := logger.newEntry() - entry.Infoln(args...) - logger.releaseEntry(entry) - } + logger.Logln(InfoLevel, args...) } func (logger *Logger) Println(args ...interface{}) { @@ -261,44 +260,32 @@ func (logger *Logger) Println(args ...interface{}) { } func (logger *Logger) Warnln(args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warnln(args...) - logger.releaseEntry(entry) - } + logger.Logln(WarnLevel, args...) } func (logger *Logger) Warningln(args ...interface{}) { - if logger.level() >= WarnLevel { - entry := logger.newEntry() - entry.Warnln(args...) - logger.releaseEntry(entry) - } + logger.Warnln(args...) } func (logger *Logger) Errorln(args ...interface{}) { - if logger.level() >= ErrorLevel { - entry := logger.newEntry() - entry.Errorln(args...) - logger.releaseEntry(entry) - } + logger.Logln(ErrorLevel, args...) } func (logger *Logger) Fatalln(args ...interface{}) { - if logger.level() >= FatalLevel { - entry := logger.newEntry() - entry.Fatalln(args...) - logger.releaseEntry(entry) - } - Exit(1) + logger.Logln(FatalLevel, args...) + logger.Exit(1) } func (logger *Logger) Panicln(args ...interface{}) { - if logger.level() >= PanicLevel { - entry := logger.newEntry() - entry.Panicln(args...) - logger.releaseEntry(entry) + logger.Logln(PanicLevel, args...) +} + +func (logger *Logger) Exit(code int) { + runHandlers() + if logger.ExitFunc == nil { + logger.ExitFunc = os.Exit } + logger.ExitFunc(code) } //When file is opened with appending mode, it's safe to @@ -312,6 +299,53 @@ func (logger *Logger) level() Level { return Level(atomic.LoadUint32((*uint32)(&logger.Level))) } -func (logger *Logger) setLevel(level Level) { +// SetLevel sets the logger level. +func (logger *Logger) SetLevel(level Level) { atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) } + +// GetLevel returns the logger level. +func (logger *Logger) GetLevel() Level { + return logger.level() +} + +// AddHook adds a hook to the logger hooks. +func (logger *Logger) AddHook(hook Hook) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Hooks.Add(hook) +} + +// IsLevelEnabled checks if the log level of the logger is greater than the level param +func (logger *Logger) IsLevelEnabled(level Level) bool { + return logger.level() >= level +} + +// SetFormatter sets the logger formatter. +func (logger *Logger) SetFormatter(formatter Formatter) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Formatter = formatter +} + +// SetOutput sets the logger output. +func (logger *Logger) SetOutput(output io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = output +} + +func (logger *Logger) SetReportCaller(reportCaller bool) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.ReportCaller = reportCaller +} + +// ReplaceHooks replaces the logger hooks and returns the old ones +func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { + logger.mu.Lock() + oldHooks := logger.Hooks + logger.Hooks = hooks + logger.mu.Unlock() + return oldHooks +} diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go index dd3899974..8644761f7 100644 --- a/vendor/github.com/sirupsen/logrus/logrus.go +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -14,22 +14,11 @@ type Level uint32 // Convert the Level to a string. E.g. PanicLevel becomes "panic". func (level Level) String() string { - switch level { - case DebugLevel: - return "debug" - case InfoLevel: - return "info" - case WarnLevel: - return "warning" - case ErrorLevel: - return "error" - case FatalLevel: - return "fatal" - case PanicLevel: - return "panic" + if b, err := level.MarshalText(); err == nil { + return string(b) + } else { + return "unknown" } - - return "unknown" } // ParseLevel takes a string level and returns the Logrus log level constant. @@ -47,12 +36,47 @@ func ParseLevel(lvl string) (Level, error) { return InfoLevel, nil case "debug": return DebugLevel, nil + case "trace": + return TraceLevel, nil } var l Level return l, fmt.Errorf("not a valid logrus Level: %q", lvl) } +// UnmarshalText implements encoding.TextUnmarshaler. +func (level *Level) UnmarshalText(text []byte) error { + l, err := ParseLevel(string(text)) + if err != nil { + return err + } + + *level = Level(l) + + return nil +} + +func (level Level) MarshalText() ([]byte, error) { + switch level { + case TraceLevel: + return []byte("trace"), nil + case DebugLevel: + return []byte("debug"), nil + case InfoLevel: + return []byte("info"), nil + case WarnLevel: + return []byte("warning"), nil + case ErrorLevel: + return []byte("error"), nil + case FatalLevel: + return []byte("fatal"), nil + case PanicLevel: + return []byte("panic"), nil + } + + return nil, fmt.Errorf("not a valid logrus level %d", level) +} + // A constant exposing all logging levels var AllLevels = []Level{ PanicLevel, @@ -61,6 +85,7 @@ var AllLevels = []Level{ WarnLevel, InfoLevel, DebugLevel, + TraceLevel, } // These are the different logging levels. You can set the logging level to log @@ -69,7 +94,7 @@ const ( // PanicLevel level, highest level of severity. Logs and then calls panic with the // message passed to Debug, Info, ... PanicLevel Level = iota - // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the // logging level is set to Panic. FatalLevel // ErrorLevel level. Logs. Used for errors that should definitely be noted. @@ -82,6 +107,8 @@ const ( InfoLevel // DebugLevel level. Usually only enabled when debugging. Very verbose logging. DebugLevel + // TraceLevel level. Designates finer-grained informational events than the Debug. + TraceLevel ) // Won't compile if StdLogger can't be realized by a log.Logger @@ -140,4 +167,20 @@ type FieldLogger interface { Errorln(args ...interface{}) Fatalln(args ...interface{}) Panicln(args ...interface{}) + + // IsDebugEnabled() bool + // IsInfoEnabled() bool + // IsWarnEnabled() bool + // IsErrorEnabled() bool + // IsFatalEnabled() bool + // IsPanicEnabled() bool +} + +// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is +// here for consistancy. Do not use. Use Logger or Entry instead. +type Ext1FieldLogger interface { + FieldLogger + Tracef(format string, args ...interface{}) + Trace(args ...interface{}) + Traceln(args ...interface{}) } diff --git a/vendor/github.com/sirupsen/logrus/terminal_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_appengine.go deleted file mode 100644 index e011a8694..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_appengine.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build appengine - -package logrus - -import "io" - -// IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal(f io.Writer) bool { - return true -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_bsd.go deleted file mode 100644 index 5f6be4d3c..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_bsd.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine - -package logrus - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA - -type Termios syscall.Termios diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go new file mode 100644 index 000000000..2403de981 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go @@ -0,0 +1,11 @@ +// +build appengine + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return true +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go new file mode 100644 index 000000000..3c4f43f91 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go @@ -0,0 +1,13 @@ +// +build darwin dragonfly freebsd netbsd openbsd + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} + diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go new file mode 100644 index 000000000..97af92c68 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go @@ -0,0 +1,11 @@ +// +build js nacl plan9 + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go new file mode 100644 index 000000000..3293fb3ca --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go @@ -0,0 +1,17 @@ +// +build !appengine,!js,!windows,!nacl,!plan9 + +package logrus + +import ( + "io" + "os" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + return isTerminal(int(v.Fd())) + default: + return false + } +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go new file mode 100644 index 000000000..f6710b3bd --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go @@ -0,0 +1,11 @@ +package logrus + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go new file mode 100644 index 000000000..355dc966f --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go @@ -0,0 +1,13 @@ +// +build linux aix + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} + diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_windows.go b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go new file mode 100644 index 000000000..572889db2 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go @@ -0,0 +1,34 @@ +// +build !appengine,!js,windows + +package logrus + +import ( + "io" + "os" + "syscall" + + sequences "github.com/konsorten/go-windows-terminal-sequences" +) + +func initTerminal(w io.Writer) { + switch v := w.(type) { + case *os.File: + sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true) + } +} + +func checkIfTerminal(w io.Writer) bool { + var ret bool + switch v := w.(type) { + case *os.File: + var mode uint32 + err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode) + ret = (err == nil) + default: + ret = false + } + if ret { + initTerminal(w) + } + return ret +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_linux.go b/vendor/github.com/sirupsen/logrus/terminal_linux.go deleted file mode 100644 index 308160ca8..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_linux.go +++ /dev/null @@ -1,14 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine - -package logrus - -import "syscall" - -const ioctlReadTermios = syscall.TCGETS - -type Termios syscall.Termios diff --git a/vendor/github.com/sirupsen/logrus/terminal_notwindows.go b/vendor/github.com/sirupsen/logrus/terminal_notwindows.go deleted file mode 100644 index 190297abf..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_notwindows.go +++ /dev/null @@ -1,28 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux darwin freebsd openbsd netbsd dragonfly -// +build !appengine - -package logrus - -import ( - "io" - "os" - "syscall" - "unsafe" -) - -// IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal(f io.Writer) bool { - var termios Termios - switch v := f.(type) { - case *os.File: - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 - default: - return false - } -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_solaris.go b/vendor/github.com/sirupsen/logrus/terminal_solaris.go deleted file mode 100644 index 3c86b1abe..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_solaris.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build solaris,!appengine - -package logrus - -import ( - "io" - "os" - - "golang.org/x/sys/unix" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -func IsTerminal(f io.Writer) bool { - switch v := f.(type) { - case *os.File: - _, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA) - return err == nil - default: - return false - } -} diff --git a/vendor/github.com/sirupsen/logrus/terminal_windows.go b/vendor/github.com/sirupsen/logrus/terminal_windows.go deleted file mode 100644 index 7a336307e..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_windows.go +++ /dev/null @@ -1,82 +0,0 @@ -// Based on ssh/terminal: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build windows,!appengine - -package logrus - -import ( - "bytes" - "errors" - "io" - "os" - "os/exec" - "strconv" - "strings" - "syscall" - "unsafe" -) - -var kernel32 = syscall.NewLazyDLL("kernel32.dll") - -var ( - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procSetConsoleMode = kernel32.NewProc("SetConsoleMode") -) - -const ( - enableProcessedOutput = 0x0001 - enableWrapAtEolOutput = 0x0002 - enableVirtualTerminalProcessing = 0x0004 -) - -func getVersion() (float64, error) { - stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} - cmd := exec.Command("cmd", "ver") - cmd.Stdout = stdout - cmd.Stderr = stderr - err := cmd.Run() - if err != nil { - return -1, err - } - - // The output should be like "Microsoft Windows [Version XX.X.XXXXXX]" - version := strings.Replace(stdout.String(), "\n", "", -1) - version = strings.Replace(version, "\r\n", "", -1) - - x1 := strings.Index(version, "[Version") - - if x1 == -1 || strings.Index(version, "]") == -1 { - return -1, errors.New("Can't determine Windows version") - } - - return strconv.ParseFloat(version[x1+9:x1+13], 64) -} - -func init() { - ver, err := getVersion() - if err != nil { - return - } - - // Activate Virtual Processing for Windows CMD - // Info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - if ver >= 10 { - handle := syscall.Handle(os.Stderr.Fd()) - procSetConsoleMode.Call(uintptr(handle), enableProcessedOutput|enableWrapAtEolOutput|enableVirtualTerminalProcessing) - } -} - -// IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal(f io.Writer) bool { - switch v := f.(type) { - case *os.File: - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 - default: - return false - } -} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go index ba8885406..e01587c43 100644 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -3,6 +3,8 @@ package logrus import ( "bytes" "fmt" + "os" + "runtime" "sort" "strings" "sync" @@ -10,22 +12,19 @@ import ( ) const ( - nocolor = 0 - red = 31 - green = 32 - yellow = 33 - blue = 34 - gray = 37 + red = 31 + yellow = 33 + blue = 36 + gray = 37 ) -var ( - baseTimestamp time.Time -) +var baseTimestamp time.Time func init() { baseTimestamp = time.Now() } +// TextFormatter formats logs into text type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. ForceColors bool @@ -33,6 +32,9 @@ type TextFormatter struct { // Force disabling colors. DisableColors bool + // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ + EnvironmentOverrideColors bool + // Disable timestamp logging. useful when output is redirected to logging // system that already adds timestamps. DisableTimestamp bool @@ -49,66 +51,151 @@ type TextFormatter struct { // be desired. DisableSorting bool + // The keys sorting function, when uninitialized it uses sort.Strings. + SortingFunc func([]string) + + // Disables the truncation of the level text to 4 characters. + DisableLevelTruncation bool + // QuoteEmptyFields will wrap empty fields in quotes if true QuoteEmptyFields bool - // QuoteCharacter can be set to the override the default quoting character " - // with something else. For example: ', or `. - QuoteCharacter string - // Whether the logger's out is to a terminal isTerminal bool - sync.Once + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &TextFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message"}} + FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + terminalInitOnce sync.Once } func (f *TextFormatter) init(entry *Entry) { - if len(f.QuoteCharacter) == 0 { - f.QuoteCharacter = "\"" - } if entry.Logger != nil { - f.isTerminal = IsTerminal(entry.Logger.Out) + f.isTerminal = checkIfTerminal(entry.Logger.Out) } } +func (f *TextFormatter) isColored() bool { + isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) + + if f.EnvironmentOverrideColors { + if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" { + isColored = true + } else if ok && force == "0" { + isColored = false + } else if os.Getenv("CLICOLOR") == "0" { + isColored = false + } + } + + return isColored && !f.DisableColors +} + +// Format renders a single log entry func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { - var b *bytes.Buffer - keys := make([]string, 0, len(entry.Data)) - for k := range entry.Data { + data := make(Fields) + for k, v := range entry.Data { + data[k] = v + } + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) + keys := make([]string, 0, len(data)) + for k := range data { keys = append(keys, k) } + var funcVal, fileVal string + + fixedKeys := make([]string, 0, 4+len(data)) + if !f.DisableTimestamp { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) + } + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) + if entry.Message != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) + } + if entry.err != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) + } + if entry.HasCaller() { + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } else { + funcVal = entry.Caller.Function + fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + } + + if funcVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) + } + if fileVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) + } + } + if !f.DisableSorting { - sort.Strings(keys) + if f.SortingFunc == nil { + sort.Strings(keys) + fixedKeys = append(fixedKeys, keys...) + } else { + if !f.isColored() { + fixedKeys = append(fixedKeys, keys...) + f.SortingFunc(fixedKeys) + } else { + f.SortingFunc(keys) + } + } + } else { + fixedKeys = append(fixedKeys, keys...) } + + var b *bytes.Buffer if entry.Buffer != nil { b = entry.Buffer } else { b = &bytes.Buffer{} } - prefixFieldClashes(entry.Data) - - f.Do(func() { f.init(entry) }) - - isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors + f.terminalInitOnce.Do(func() { f.init(entry) }) timestampFormat := f.TimestampFormat if timestampFormat == "" { - timestampFormat = DefaultTimestampFormat + timestampFormat = defaultTimestampFormat } - if isColored { - f.printColored(b, entry, keys, timestampFormat) + if f.isColored() { + f.printColored(b, entry, keys, data, timestampFormat) } else { - if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) - } - f.appendKeyValue(b, "level", entry.Level.String()) - if entry.Message != "" { - f.appendKeyValue(b, "msg", entry.Message) - } - for _, key := range keys { - f.appendKeyValue(b, key, entry.Data[key]) + + for _, key := range fixedKeys { + var value interface{} + switch { + case key == f.FieldMap.resolve(FieldKeyTime): + value = entry.Time.Format(timestampFormat) + case key == f.FieldMap.resolve(FieldKeyLevel): + value = entry.Level.String() + case key == f.FieldMap.resolve(FieldKeyMsg): + value = entry.Message + case key == f.FieldMap.resolve(FieldKeyLogrusError): + value = entry.err + case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): + value = funcVal + case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): + value = fileVal + default: + value = data[key] + } + f.appendKeyValue(b, key, value) } } @@ -116,10 +203,10 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { return b.Bytes(), nil } -func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { var levelColor int switch entry.Level { - case DebugLevel: + case DebugLevel, TraceLevel: levelColor = gray case WarnLevel: levelColor = yellow @@ -129,17 +216,42 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin levelColor = blue } - levelText := strings.ToUpper(entry.Level.String())[0:4] + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableLevelTruncation { + levelText = levelText[0:4] + } + + // Remove a single newline if it already exists in the message to keep + // the behavior of logrus text_formatter the same as the stdlib log package + entry.Message = strings.TrimSuffix(entry.Message, "\n") + + caller := "" + if entry.HasCaller() { + funcVal := fmt.Sprintf("%s()", entry.Caller.Function) + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + + if fileVal == "" { + caller = funcVal + } else if funcVal == "" { + caller = fileVal + } else { + caller = fileVal + " " + funcVal + } + } if f.DisableTimestamp { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) } else if !f.FullTimestamp { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) } else { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) } for _, k := range keys { - v := entry.Data[k] + v := data[k] fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) f.appendValue(b, v) } @@ -153,7 +265,7 @@ func (f *TextFormatter) needsQuoting(text string) bool { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || - ch == '-' || ch == '.') { + ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { return true } } @@ -161,29 +273,23 @@ func (f *TextFormatter) needsQuoting(text string) bool { } func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { - + if b.Len() > 0 { + b.WriteByte(' ') + } b.WriteString(key) b.WriteByte('=') f.appendValue(b, value) - b.WriteByte(' ') } func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { - switch value := value.(type) { - case string: - if !f.needsQuoting(value) { - b.WriteString(value) - } else { - fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter) - } - case error: - errmsg := value.Error() - if !f.needsQuoting(errmsg) { - b.WriteString(errmsg) - } else { - fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter) - } - default: - fmt.Fprint(b, value) + stringVal, ok := value.(string) + if !ok { + stringVal = fmt.Sprint(value) + } + + if !f.needsQuoting(stringVal) { + b.WriteString(stringVal) + } else { + b.WriteString(fmt.Sprintf("%q", stringVal)) } } diff --git a/vendor/github.com/sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go index 7bdebedc6..9e1f75135 100644 --- a/vendor/github.com/sirupsen/logrus/writer.go +++ b/vendor/github.com/sirupsen/logrus/writer.go @@ -24,6 +24,8 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { var printFunc func(args ...interface{}) switch level { + case TraceLevel: + printFunc = entry.Trace case DebugLevel: printFunc = entry.Debug case InfoLevel: diff --git a/version/version.go b/version/version.go index 2ef7a9c65..3dbf4768d 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.4.2-dev" +const Version = "1.4.3-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |