diff options
114 files changed, 269 insertions, 7874 deletions
@@ -25,14 +25,12 @@ pwd # -t integration test # -u unit test # -v validate -# -p run python tests build=0 install=0 integrationtest=0 unittest=0 validate=0 -runpython=0 options=0 install_tools_made=0 @@ -44,9 +42,6 @@ while getopts "biptuv" opt; do i) install=1 options=1 ;; - p) runpython=1 - options=1 - ;; t) integrationtest=1 options=1 ;; @@ -124,18 +119,11 @@ if [ $install -eq 1 ]; then make TAGS="${TAGS}" install.man PREFIX=/usr ETCDIR=/etc make TAGS="${TAGS}" install.cni PREFIX=/usr ETCDIR=/etc make TAGS="${TAGS}" install.systemd PREFIX=/usr ETCDIR=/etc - if [ $runpython -eq 1 ]; then - make TAGS="${TAGS}" install.python PREFIX=/usr ETCDIR=/etc - fi - fi # Run integration tests if [ $integrationtest -eq 1 ]; then make TAGS="${TAGS}" test-binaries make varlink_generate GOPATH=/go - if [ $runpython -eq 1 ]; then - make clientintegration GOPATH=/go - fi make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS fi diff --git a/.papr_prepare.sh b/.papr_prepare.sh index 5d7d21530..b93f7b91f 100644 --- a/.papr_prepare.sh +++ b/.papr_prepare.sh @@ -4,10 +4,8 @@ set -xeuo pipefail DIST=${DIST:=Fedora} CONTAINER_RUNTIME=${CONTAINER_RUNTIME:=docker} IMAGE=fedorapodmanbuild -PYTHON=python3 if [[ ${DIST} != "Fedora" ]]; then IMAGE=centospodmanbuild - PYTHON=python fi # Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore @@ -21,4 +19,4 @@ modprobe iptable_nat || : ${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . 2>build.log # Run the tests -${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e PYTHON=$PYTHON -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t +${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t diff --git a/Dockerfile b/Dockerfile index c227207bd..59b5d5da3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,6 @@ RUN apt-get update && apt-get install -y \ libudev-dev \ protobuf-c-compiler \ protobuf-compiler \ - python-minimal \ libglib2.0-dev \ libapparmor-dev \ btrfs-tools \ @@ -37,11 +36,6 @@ RUN apt-get update && apt-get install -y \ liblzma-dev \ netcat \ socat \ - python3-pip \ - python3-dateutil \ - python3-setuptools \ - python3-psutil \ - python3-pytoml \ lsof \ xz-utils \ --no-install-recommends \ @@ -129,9 +123,6 @@ COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist # Make sure we have some policy for pulling images RUN mkdir -p /etc/containers && curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf -# Install python3 varlink module from pypi -RUN pip3 install varlink - COPY test/policy.json /etc/containers/policy.json COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS index 3e14e59db..be4ae3eaf 100644 --- a/Dockerfile.CentOS +++ b/Dockerfile.CentOS @@ -19,10 +19,6 @@ RUN yum -y install btrfs-progs-devel \ runc \ make \ ostree-devel \ - python \ - python3-dateutil \ - python3-psutil \ - python3-pytoml \ lsof \ which\ golang-github-cpuguy83-go-md2man \ diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora index 2080b597b..aeee9c3cf 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.Fedora @@ -20,10 +20,6 @@ RUN dnf -y install btrfs-progs-devel \ runc \ make \ ostree-devel \ - python \ - python3-dateutil \ - python3-psutil \ - python3-pytoml \ lsof \ which\ golang-github-cpuguy83-go-md2man \ @@ -26,8 +26,6 @@ ifneq (,$(findstring varlink,$(BUILDTAGS))) endif CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker) -HAS_PYTHON3 := $(shell command -v python3 2>/dev/null) - BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d @@ -88,9 +86,6 @@ endif lint: .gopathok varlink_generate @echo "checking lint" @./.tool/lint - # Not ready - # @$(MAKE) -C contrib/python/podman lint - # @$(MAKE) -C contrib/python/pypodman lint gofmt: find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+ @@ -122,12 +117,6 @@ bin/podman.cross.%: .gopathok GOARCH="$${TARGET##*.}" \ $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags '$(BUILDTAGS_CROSS)' -o "$@" $(PROJECT)/cmd/podman -python: -ifdef HAS_PYTHON3 - $(MAKE) -C contrib/python/podman python-podman - $(MAKE) -C contrib/python/pypodman python-pypodman -endif - clean: rm -rf \ .gopathok \ @@ -147,8 +136,6 @@ clean: $(MANPAGES) ||: find . -name \*~ -delete find . -name \#\* -delete - $(MAKE) -C contrib/python/podman clean - $(MAKE) -C contrib/python/pypodman clean libpodimage: ${CONTAINER_RUNTIME} build -t ${LIBPOD_IMAGE} . @@ -181,7 +168,7 @@ localunit: test/goecho/goecho varlink_generate ginkgo: ginkgo -v -tags "$(BUILDTAGS)" -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. -localintegration: varlink_generate test-binaries clientintegration ginkgo +localintegration: varlink_generate test-binaries ginkgo localsystem: .install.ginkgo .install.gomega ginkgo -v -noColor test/system/ @@ -189,10 +176,6 @@ localsystem: .install.ginkgo .install.gomega system.test-binary: .install.ginkgo .install.gomega $(GO) test -c ./test/system -clientintegration: - $(MAKE) -C contrib/python/podman integration - $(MAKE) -C contrib/python/pypodman integration - perftest: $ cd contrib/perftest;go build @@ -230,7 +213,7 @@ changelog: $(shell cat $(TMPFILE) >> changelog.txt) $(shell rm $(TMPFILE)) -install: .gopathok install.bin install.man install.cni install.systemd install.python +install: .gopathok install.bin install.man install.cni install.systemd install.bin: install ${SELINUXOPT} -d -m 755 $(BINDIR) @@ -268,10 +251,6 @@ install.systemd: install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${SYSTEMDDIR}/io.podman.service install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${TMPFILESDIR}/podman.conf -install.python: - $(MAKE) DESTDIR=${DESTDIR} -C contrib/python/podman install - $(MAKE) DESTDIR=${DESTDIR} -C contrib/python/pypodman install - uninstall: for i in $(filter %.1,$(MANPAGES)); do \ rm -f $(MANDIR)/man1/$$(basename $${i}); \ @@ -279,8 +258,6 @@ uninstall: for i in $(filter %.5,$(MANPAGES)); do \ rm -f $(MANDIR)/man5/$$(basename $${i}); \ done - $(MAKE) -C contrib/python/pypodman uninstall - $(MAKE) -C contrib/python/podman uninstall .PHONY: .gitvalidation .gitvalidation: .gopathok @@ -356,7 +333,10 @@ cmd/podman/varlink/iopodman.go: cmd/podman/varlink/io.podman.varlink API.md: cmd/podman/varlink/io.podman.varlink $(GO) generate ./docs/... -validate: gofmt .gitvalidation +validate.completions: completions/bash/podman + . completions/bash/podman + +validate: gofmt .gitvalidation validate.completions build-all-new-commits: # Validate that all the commits build on top of $(GIT_BASE_BRANCH) @@ -366,6 +346,7 @@ build-all-new-commits: .gopathok \ binaries \ clean \ + validate.completions \ default \ docs \ gofmt \ @@ -378,5 +359,3 @@ build-all-new-commits: changelog \ validate \ install.libseccomp.sudo \ - python \ - clientintegration @@ -23,23 +23,22 @@ At a high level, the scope of libpod and podman is the following: ## Roadmap -1. Python frontend for Varlink API +1. Allow the Podman CLI to use a Varlink backend to connect to remote Podman instances 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. In-memory locking to replace file locks ## Out of scope * Signing and pushing images to various image storages. See [Skopeo](https://github.com/containers/skopeo/). -* Container Runtimes daemons for working with Kubernetes CRIs. +* Container Runtimes daemons for working with the Kubernetes CRI interface. See [CRI-O](https://github.com/kubernetes-sigs/cri-o). ## OCI Projects Plans The plan is to use OCI projects and best of breed libraries for different aspects: -- Runtime: [runc](https://github.com/opencontainers/runc) (or any OCI compliant runtime) and [oci runtime tools](https://github.com/opencontainers/runtime-tools) to generate the spec +- Runtime: [runc](https://github.com/opencontainers/runc) (or any OCI compliant runtime) and [OCI runtime tools](https://github.com/opencontainers/runtime-tools) to generate the spec - Images: Image management using [containers/image](https://github.com/containers/image) - Storage: Container and image storage is managed by [containers/storage](https://github.com/containers/storage) - Networking: Networking support through use of [CNI](https://github.com/containernetworking/cni) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 43804ee35..604404827 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -148,16 +148,20 @@ func main() { logrus.SetLevel(level) } - // Only if not rootless, set rlimits for open files. - // We open numerous FDs for ports opened - if !rootless.IsRootless() { - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") + } + rlimits.Cur = rlimits.Max if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { return errors.Wrapf(err, "error setting new rlimits") } - } else { + } + + if rootless.IsRootless() { logrus.Info("running as rootless") } diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 8a31ddb98..1d9aecdc9 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -1,10 +1,10 @@ package main import ( - "fmt" "io/ioutil" "net/url" "os" + "path/filepath" "strconv" "strings" @@ -59,7 +59,7 @@ func signCmd(c *cli.Context) error { signby := c.String("sign-by") if signby == "" { - return errors.Errorf("You must provide an identity") + return errors.Errorf("please provide an identity") } var sigStoreDir string @@ -72,11 +72,11 @@ func signCmd(c *cli.Context) error { mech, err := signature.NewGPGSigningMechanism() if err != nil { - return errors.Wrap(err, "Error initializing GPG") + return errors.Wrap(err, "error initializing GPG") } defer mech.Close() if err := mech.SupportsSigning(); err != nil { - return errors.Wrap(err, "Signing is not supported") + return errors.Wrap(err, "signing is not supported") } systemRegistriesDirPath := trust.RegistriesDirPath(runtime.SystemContext()) @@ -100,7 +100,7 @@ func signCmd(c *cli.Context) error { } dockerReference := rawSource.Reference().DockerReference() if dockerReference == nil { - return errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) } // create the signstore file @@ -141,7 +141,8 @@ func signCmd(c *cli.Context) error { return errors.Wrapf(err, "error creating new signature") } - sigStoreDir = fmt.Sprintf("%s/%s", sigStoreDir, strings.Replace(repos[0][strings.Index(repos[0], "/")+1:len(repos[0])], ":", "=", 1)) + trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) + sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) if err := os.MkdirAll(sigStoreDir, 0751); err != nil { // The directory is allowed to exist if !os.IsExist(err) { @@ -154,7 +155,7 @@ func signCmd(c *cli.Context) error { logrus.Errorf("error creating sigstore file: %v", err) continue } - err = ioutil.WriteFile(sigStoreDir+"/"+sigFilename, newSig, 0644) + err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) if err != nil { logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) continue @@ -190,7 +191,7 @@ func isValidSigStoreDir(sigStoreDir string) (string, error) { } _, exists := writeURIs[url.Scheme] if !exists { - return sigStoreDir, errors.Errorf("Writing to %s is not supported. Use a supported scheme", sigStoreDir) + return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) } sigStoreDir = url.Path return sigStoreDir, nil diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go index 7c404cd3f..863f36d09 100644 --- a/cmd/podman/trust.go +++ b/cmd/podman/trust.go @@ -13,7 +13,6 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -132,7 +131,7 @@ func showTrustCmd(c *cli.Context) error { if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { return errors.Errorf("could not read trust policies") } - policyJSON, err := trust.GetPolicyJSON(policyContentStruct, systemRegistriesDirPath) + policyJSON, showOutputMap, err := trust.GetPolicy(policyContentStruct, systemRegistriesDirPath) if err != nil { return errors.Wrapf(err, "error reading registry config file") } @@ -144,31 +143,12 @@ func showTrustCmd(c *cli.Context) error { } sortedRepos := sortPolicyJSONKey(policyJSON) - type policydefault struct { - Repo string - Trusttype string - GPGid string - Sigstore string - } - var policyoutput []policydefault - for _, repo := range sortedRepos { - repoval := policyJSON[repo] - var defaultstruct policydefault - defaultstruct.Repo = repo - if repoval["type"] != nil { - defaultstruct.Trusttype = trustTypeDescription(repoval["type"].(string)) - } - if repoval["keys"] != nil && len(repoval["keys"].([]string)) > 0 { - defaultstruct.GPGid = trust.GetGPGId(repoval["keys"].([]string)) - } - if repoval["sigstore"] != nil { - defaultstruct.Sigstore = repoval["sigstore"].(string) - } - policyoutput = append(policyoutput, defaultstruct) - } var output []interface{} - for _, ele := range policyoutput { - output = append(output, interface{}(ele)) + for _, reponame := range sortedRepos { + showOutput, exists := showOutputMap[reponame] + if exists { + output = append(output, interface{}(showOutput)) + } } out := formats.StdoutTemplateArray{Output: output, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} return formats.Writer(out).Out() @@ -209,8 +189,10 @@ func setTrustCmd(c *cli.Context) error { policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) } var policyContentStruct trust.PolicyContent + policyFileExists := false _, err = os.Stat(policyPath) if !os.IsNotExist(err) { + policyFileExists = true policyContent, err := ioutil.ReadFile(policyPath) if err != nil { return errors.Wrapf(err, "unable to read %s", policyPath) @@ -218,6 +200,9 @@ func setTrustCmd(c *cli.Context) error { if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { return errors.Errorf("could not read trust policies") } + if args[0] != "default" && len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set.") + } } var newReposContent []trust.RepoContent if len(pubkeysfile) != 0 { @@ -230,15 +215,18 @@ func setTrustCmd(c *cli.Context) error { if args[0] == "default" { policyContentStruct.Default = newReposContent } else { - exists := false + if policyFileExists == false && len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set to create the policy file.") + } + registryExists := false for transport, transportval := range policyContentStruct.Transports { - _, exists = transportval[args[0]] - if exists { + _, registryExists = transportval[args[0]] + if registryExists { policyContentStruct.Transports[transport][args[0]] = newReposContent break } } - if !exists { + if !registryExists { if policyContentStruct.Transports == nil { policyContentStruct.Transports = make(map[string]trust.RepoMap) } @@ -260,16 +248,6 @@ func setTrustCmd(c *cli.Context) error { return nil } -var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} - -func trustTypeDescription(trustType string) string { - trustDescription, exist := typeDescription[trustType] - if !exist { - logrus.Warnf("invalid trust type %s", trustType) - } - return trustDescription -} - func sortPolicyJSONKey(m map[string]map[string]interface{}) []string { keys := make([]string, len(m)) i := 0 diff --git a/completions/bash/podman b/completions/bash/podman index e23615d52..6333dfdf2 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -32,6 +32,9 @@ __podman_containers() { __podman_q ps --format "$format" "$@" } +__podman_list_registries() { + sed -n -e '/registries.*=/ {s/.*\[\([^]]*\).*/\1/p;q}' /etc/containers/registries.conf | sed -e "s/[,']//g" +} # __podman_pods returns a list of pods. Additional options to # `podman pod ps` may be specified in order to filter the list, e.g. @@ -365,6 +368,7 @@ __podman_subcommands() { local subcommands="$1" local counter=$(($command_pos + 1)) + while [ $counter -lt $cword ]; do case "${words[$counter]}" in $(__podman_to_extglob "$subcommands") ) @@ -1296,7 +1300,9 @@ _podman_image() { push rm save + sign tag + trust " local aliases=" list @@ -2356,6 +2362,92 @@ _podman_container_runlabel() { esac } +_podman_image_sign() { + local options_with_args=" + -d + --directory + --sign-by + " + local boolean_options=" + --help + -h + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac +} + +_podman_image_trust_set() { + echo hello + local options_with_args=" + -f + --type + --pubkeysfile + " + local boolean_options=" + --help + -h + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "default $( __podman_list_registries )" -- "$cur")) + ;; + esac +} + +_podman_image_trust_show() { + local options_with_args=" + " + local boolean_options=" + --help + -h + -j + --json + --raw + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac +} + +_podman_image_trust() { + local boolean_options=" + --help + -h + " + subcommands=" + set + show + " + local aliases=" + list + " + command=image_trust + __podman_subcommands "$subcommands $aliases" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} + _podman_images_prune() { local options_with_args=" " @@ -2364,6 +2456,11 @@ _podman_images_prune() { -h --help " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac } _podman_container_prune() { @@ -2382,6 +2479,15 @@ _podman_container_exists() { local boolean_options=" " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac + } _podman_pod_exists() { @@ -2813,6 +2919,7 @@ _podman_podman() { export generate history + image images import info diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 9e9639223..d50c1e1a3 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -17,6 +17,7 @@ PACKER_BASE=${PACKER_BASE:-./contrib/cirrus/packer} CIRRUS_BUILD_ID=${CIRRUS_BUILD_ID:-DEADBEEF} # a human CIRRUS_BASE_SHA=${CIRRUS_BASE_SHA:-HEAD} CIRRUS_CHANGE_IN_REPO=${CIRRUS_CHANGE_IN_REPO:-FETCH_HEAD} +START_STAMP_FILEPATH="${START_STAMP_FILEPATH:-/var/tmp/start.timestamp}" if ! [[ "$PATH" =~ "/usr/local/bin" ]] then @@ -135,6 +136,13 @@ ircmsg() { set -e } +start_timestamp() { + req_env_var "START_STAMP_FILEPATH $START_STAMP_FILEPATH" + [[ -r "$START_STAMP_FILEPATH" ]] || \ + echo -e ".\nThe time at the tone will be:\n$(date --iso-8601=seconds | \ + tee $START_STAMP_FILEPATH)\nBLEEEEEEEEEEP!\n.\n" # Cirrus strips blank lines from output +} + # Run sudo in directory with GOPATH set cdsudo() { DIR="$1" diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 174bd3daf..5899dca2d 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -1,8 +1,11 @@ #!/bin/bash set -e + source $(dirname $0)/lib.sh +start_timestamp + req_env_var " USER $USER HOME $HOME diff --git a/contrib/python/.gitignore b/contrib/python/.gitignore deleted file mode 100644 index a3903c542..000000000 --- a/contrib/python/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build -dist -*.egg-info -*.pyc -__pycache__ diff --git a/contrib/python/podman/.pylintrc b/contrib/python/podman/.pylintrc deleted file mode 100644 index a5628a6cf..000000000 --- a/contrib/python/podman/.pylintrc +++ /dev/null @@ -1,564 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=0 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - locally-enabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )?<?https?://\S+>?$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[BASIC] - -# Naming style matching correct argument names. -#argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -argument-rgx=[a-z_][a-z0-9_]{1,30}$ -argument-name-hint=[a-z_][a-z0-9_]{1,30}$ - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=c, - e, - i, - j, - k, - r, - v, - ex, - Run, - _ - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -#variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -variable-rgx=[a-z_][a-z0-9_]{2,30}$ -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[DESIGN] - -# Support argparse.Action constructor API -# Maximum number of arguments for function / method. -max-args=12 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=10 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception". -overgeneral-exceptions=Exception diff --git a/contrib/python/podman/CHANGES.txt b/contrib/python/podman/CHANGES.txt deleted file mode 100644 index 2bac1c867..000000000 --- a/contrib/python/podman/CHANGES.txt +++ /dev/null @@ -1 +0,0 @@ -v0.1.0, 2018-05-11 -- Initial release. diff --git a/contrib/python/podman/LICENSE.txt b/contrib/python/podman/LICENSE.txt deleted file mode 100644 index decfce56d..000000000 --- a/contrib/python/podman/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2018 Red Hat, Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/contrib/python/podman/MANIFEST.in b/contrib/python/podman/MANIFEST.in deleted file mode 100644 index a5897de50..000000000 --- a/contrib/python/podman/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -prune test/ -include README.md -include requirements.txt diff --git a/contrib/python/podman/Makefile b/contrib/python/podman/Makefile deleted file mode 100644 index 0cbfe2fb3..000000000 --- a/contrib/python/podman/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -PYTHON ?= $(shell command -v python3 2>/dev/null || command -v python) -DESTDIR ?= / -PODMAN_VERSION ?= '0.11.1.1' - -.PHONY: python-podman -python-podman: - PODMAN_VERSION=$(PODMAN_VERSION) \ - $(PYTHON) setup.py sdist bdist - -.PHONY: lint -lint: - $(PYTHON) -m pylint podman - -.PHONY: integration -integration: - test/test_runner.sh -v - -.PHONY: install -install: - PODMAN_VERSION=$(PODMAN_VERSION) \ - $(PYTHON) setup.py install --root ${DESTDIR} - -.PHONY: upload -upload: - PODMAN_VERSION=$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel - twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/* - -.PHONY: clobber -clobber: uninstall clean - -.PHONY: uninstall -uninstall: - $(PYTHON) -m pip uninstall --yes podman ||: - -.PHONY: clean -clean: - rm -rf podman.egg-info dist - find . -depth -name __pycache__ -exec rm -rf {} \; - find . -depth -name \*.pyc -exec rm -f {} \; - $(PYTHON) ./setup.py clean --all diff --git a/contrib/python/podman/README.md b/contrib/python/podman/README.md deleted file mode 100644 index 3254064b0..000000000 --- a/contrib/python/podman/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# podman - pythonic library for working with varlink interface to Podman - -## Status: Active Development - -See [libpod](https://github.com/containers/libpod) - -## Releases - -To build the podman egg and install as user: - -```sh -cd ~/libpod/contrib/python/podman -python3 setup.py clean -a && python3 setup.py sdist bdist -python3 setup.py install --user -``` - -## Code snippets/examples: - -### Show images in storage - -```python -import podman - -with podman.Client() as client: - list(map(print, client.images.list())) -``` - -### Show containers created since midnight - -```python -from datetime import datetime, time, timezone - -import podman - -midnight = datetime.combine(datetime.today(), time.min, tzinfo=timezone.utc) - -with podman.Client() as client: - for c in client.containers.list(): - created_at = podman.datetime_parse(c.createdat) - - if created_at > midnight: - print('Container {}: image: {} created at: {}'.format( - c.id[:12], c.image[:32], podman.datetime_format(created_at))) -``` diff --git a/contrib/python/podman/examples/eg_attach.py b/contrib/python/podman/examples/eg_attach.py deleted file mode 100644 index f5070dc53..000000000 --- a/contrib/python/podman/examples/eg_attach.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Run top on Alpine container.""" - -import podman - -print('{}\n'.format(__doc__)) - -with podman.Client() as client: - id = client.images.pull('alpine:latest') - img = client.images.get(id) - cntr = img.create(detach=True, tty=True, command=['/usr/bin/top']) - cntr.attach(eot=4) - - try: - cntr.start() - print() - except (BrokenPipeError, KeyboardInterrupt): - print('\nContainer disconnected.') diff --git a/contrib/python/podman/examples/eg_containers_by_image.py b/contrib/python/podman/examples/eg_containers_by_image.py deleted file mode 100644 index bf4fdebf1..000000000 --- a/contrib/python/podman/examples/eg_containers_by_image.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Show containers grouped by image id.""" - -from itertools import groupby - -import podman - -print('{}\n'.format(__doc__)) - -with podman.Client() as client: - ctnrs = sorted(client.containers.list(), key=lambda k: k.imageid) - for key, grp in groupby(ctnrs, key=lambda k: k.imageid): - print('Image: {}'.format(key)) - for c in grp: - print(' : container: {} created at: {}'.format( - c.id[:12], podman.datetime_format(c.createdat))) diff --git a/contrib/python/podman/examples/eg_image_list.py b/contrib/python/podman/examples/eg_image_list.py deleted file mode 100644 index ef31fd708..000000000 --- a/contrib/python/podman/examples/eg_image_list.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Show all images on system.""" - -import podman - -print('{}\n'.format(__doc__)) - -with podman.Client() as client: - for img in client.images.list(): - print(img) diff --git a/contrib/python/podman/examples/eg_inspect_fedora.py b/contrib/python/podman/examples/eg_inspect_fedora.py deleted file mode 100644 index b5bbba46d..000000000 --- a/contrib/python/podman/examples/eg_inspect_fedora.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Pull Fedora and inspect image and container.""" - -import podman - -print('{}\n'.format(__doc__)) - -with podman.Client() as client: - id = client.images.pull('registry.fedoraproject.org/fedora:28') - img = client.images.get(id) - print(img.inspect()) - - cntr = img.create() - print(cntr.inspect()) - - cntr.remove() diff --git a/contrib/python/podman/examples/eg_latest_containers.py b/contrib/python/podman/examples/eg_latest_containers.py deleted file mode 100644 index 446f670dd..000000000 --- a/contrib/python/podman/examples/eg_latest_containers.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Show all containers created since midnight.""" - -from datetime import datetime, time, timezone - -import podman - -print('{}\n'.format(__doc__)) - - -midnight = datetime.combine(datetime.today(), time.min, tzinfo=timezone.utc) - -with podman.Client() as client: - for c in client.containers.list(): - created_at = podman.datetime_parse(c.createdat) - - if created_at > midnight: - print('{}: image: {} createdAt: {}'.format( - c.id[:12], c.image[:32], podman.datetime_format(created_at))) diff --git a/contrib/python/podman/examples/eg_new_image.py b/contrib/python/podman/examples/eg_new_image.py deleted file mode 100644 index 21e076dcb..000000000 --- a/contrib/python/podman/examples/eg_new_image.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Create new image from container.""" - -import sys - -import podman - - -def print_history(details): - """Format history data from an image, in a table.""" - for i, r in enumerate(details): - print( - '{}: {} {} {}'.format(i, r.id[:12], - podman.datetime_format(r.created), r.tags), - sep='\n') - print("-" * 25) - - -print('{}\n'.format(__doc__)) - -with podman.Client() as client: - ctnr = next( - (c for c in client.containers.list() if 'alpine' in c['image']), None) - - if ctnr: - print_history(client.images.get(ctnr.imageid).history()) - - # Make changes as we save the container to a new image - id = ctnr.commit('alpine-ash', changes=['CMD=/bin/ash']) - print_history(client.images.get(id).history()) - else: - print('Unable to find "alpine" container.', file=sys.stderr) diff --git a/contrib/python/podman/examples/run_example.sh b/contrib/python/podman/examples/run_example.sh deleted file mode 100755 index d81ddf456..000000000 --- a/contrib/python/podman/examples/run_example.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -export PYTHONPATH=.. - -function examples { - for file in $@; do - python3 -c "import ast; f=open('"${file}"'); t=ast.parse(f.read()); print(ast.get_docstring(t) + ' -- "${file}"')" - done -} - -while getopts "lh" arg; do - case $arg in - l ) examples $(ls eg_*.py); exit 0 ;; - h ) echo 1>&2 $0 [-l] [-h] filename ; exit 2 ;; - esac -done -shift $((OPTIND-1)) - -# podman needs to play some games with resources -if [[ $(id -u) != 0 ]]; then - echo 1>&2 $0 must be run as root. - exit 2 -fi - -if ! systemctl --quiet is-active io.podman.socket; then - echo 1>&2 'podman is not running. systemctl enable --now io.podman.socket' - exit 1 -fi - -function cleanup { - podman rm $1 >/dev/null 2>&1 -} - -# Setup storage with an image and container -podman pull alpine:latest >/tmp/podman.output 2>&1 -CTNR=$(podman create alpine) -trap "cleanup $CTNR" EXIT - -if [[ -f $1 ]]; then - python3 $1 -else - python3 $1.py -fi diff --git a/contrib/python/podman/podman/__init__.py b/contrib/python/podman/podman/__init__.py deleted file mode 100644 index 1cdb72773..000000000 --- a/contrib/python/podman/podman/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -"""A client for communicating with a Podman server.""" -import pkg_resources - -from .client import Client -from .libs import FoldedString, datetime_format, datetime_parse -from .libs.errors import (ContainerNotFound, ErrorOccurred, ImageNotFound, - NoContainerRunning, NoContainersInPod, - PodContainerError, PodmanError, PodNotFound) - -assert FoldedString - -try: - __version__ = pkg_resources.get_distribution('podman').version -except Exception: # pylint: disable=broad-except - __version__ = '0.0.0' - -__all__ = [ - 'Client', - 'ContainerNotFound', - 'datetime_format', - 'datetime_parse', - 'ErrorOccurred', - 'ImageNotFound', - 'NoContainerRunning', - 'NoContainersInPod', - 'PodContainerError', - 'PodmanError', - 'PodNotFound', -] diff --git a/contrib/python/podman/podman/client.py b/contrib/python/podman/podman/client.py deleted file mode 100644 index ad603166e..000000000 --- a/contrib/python/podman/podman/client.py +++ /dev/null @@ -1,212 +0,0 @@ -"""A client for communicating with a Podman varlink service.""" -import errno -import logging -import os -from urllib.parse import urlparse - -from varlink import Client as VarlinkClient -from varlink import VarlinkError - -from .libs import cached_property -from .libs.containers import Containers -from .libs.errors import error_factory -from .libs.images import Images -from .libs.system import System -from .libs.tunnel import Context, Portal, Tunnel -from .libs.pods import Pods - - -class BaseClient(): - """Context manager for API workers to access varlink.""" - - def __init__(self, context): - """Construct Client.""" - self._client = None - self._iface = None - self._context = context - - def __call__(self): - """Support being called for old API.""" - return self - - @classmethod - def factory(cls, - uri=None, - interface='io.podman', - *args, - **kwargs): - """Construct a Client based on input.""" - log_level = os.environ.get('LOG_LEVEL') - if log_level is not None: - logging.basicConfig(level=logging.getLevelName(log_level.upper())) - - if uri is None: - raise ValueError('uri is required and cannot be None') - if interface is None: - raise ValueError('interface is required and cannot be None') - - unsupported = set(kwargs.keys()).difference( - ('uri', 'interface', 'remote_uri', 'identity_file')) - if unsupported: - raise ValueError('Unknown keyword arguments: {}'.format( - ', '.join(unsupported))) - - local_path = urlparse(uri).path - if local_path == '': - raise ValueError('path is required for uri,' - ' expected format "unix://path_to_socket"') - - if kwargs.get('remote_uri') is None: - return LocalClient(Context(uri, interface)) - - required = ('{} is required, expected format' - ' "ssh://user@hostname[:port]/path_to_socket".') - - # Remote access requires the full tuple of information - if kwargs.get('remote_uri') is None: - raise ValueError(required.format('remote_uri')) - - remote = urlparse(kwargs['remote_uri']) - if remote.username is None: - raise ValueError(required.format('username')) - if remote.path == '': - raise ValueError(required.format('path')) - if remote.hostname is None: - raise ValueError(required.format('hostname')) - - return RemoteClient( - Context( - uri, - interface, - local_path, - remote.path, - remote.username, - remote.hostname, - remote.port, - kwargs.get('identity_file'), - )) - - -class LocalClient(BaseClient): - """Context manager for API workers to access varlink.""" - - def __enter__(self): - """Enter context for LocalClient.""" - self._client = VarlinkClient(address=self._context.uri) - self._iface = self._client.open(self._context.interface) - return self._iface - - def __exit__(self, e_type, e, e_traceback): - """Cleanup context for LocalClient.""" - if hasattr(self._client, 'close'): - self._client.close() - self._iface.close() - - if isinstance(e, VarlinkError): - raise error_factory(e) - - -class RemoteClient(BaseClient): - """Context manager for API workers to access remote varlink.""" - - def __init__(self, context): - """Construct RemoteCLient.""" - super().__init__(context) - self._portal = Portal() - - def __enter__(self): - """Context manager for API workers to access varlink.""" - tunnel = self._portal.get(self._context.uri) - if tunnel is None: - tunnel = Tunnel(self._context).bore() - self._portal[self._context.uri] = tunnel - - try: - self._client = VarlinkClient(address=self._context.uri) - self._iface = self._client.open(self._context.interface) - return self._iface - except Exception: - tunnel.close() - raise - - def __exit__(self, e_type, e, e_traceback): - """Cleanup context for RemoteClient.""" - if hasattr(self._client, 'close'): - self._client.close() - self._iface.close() - - # set timer to shutdown ssh tunnel - # self._portal.get(self._context.uri).close() - if isinstance(e, VarlinkError): - raise error_factory(e) - - -class Client(): - """A client for communicating with a Podman varlink service. - - Example: - - >>> import podman - >>> c = podman.Client() - >>> c.system.versions - - Example remote podman: - - >>> import podman - >>> c = podman.Client(uri='unix:/tmp/podman.sock', - remote_uri='ssh://user@host/run/podman/io.podman', - identity_file='~/.ssh/id_rsa') - """ - - def __init__(self, - uri='unix:/run/podman/io.podman', - interface='io.podman', - **kwargs): - """Construct a podman varlink Client. - - uri from default systemd unit file. - interface from io.podman.varlink, do not change unless - you are a varlink guru. - """ - self._client = BaseClient.factory(uri, interface, **kwargs) - - address = "{}-{}".format(uri, interface) - # Quick validation of connection data provided - try: - if not System(self._client).ping(): - raise ConnectionRefusedError( - errno.ECONNREFUSED, - ('Failed varlink connection "{}"').format(address)) - except FileNotFoundError: - raise ConnectionError( - errno.ECONNREFUSED, - ('Failed varlink connection "{}".' - ' Is podman socket or service running?').format(address)) - - def __enter__(self): - """Return `self` upon entering the runtime context.""" - return self - - def __exit__(self, exc_type, exc_value, traceback): - """Raise any exception triggered within the runtime context.""" - pass - - @cached_property - def system(self): - """Manage system model for podman.""" - return System(self._client) - - @cached_property - def images(self): - """Manage images model for libpod.""" - return Images(self._client) - - @cached_property - def containers(self): - """Manage containers model for libpod.""" - return Containers(self._client) - - @cached_property - def pods(self): - """Manage pods model for libpod.""" - return Pods(self._client) diff --git a/contrib/python/podman/podman/libs/__init__.py b/contrib/python/podman/podman/libs/__init__.py deleted file mode 100644 index 5193313ed..000000000 --- a/contrib/python/podman/podman/libs/__init__.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Support files for podman API implementation.""" -import collections -import datetime -import functools - -from dateutil.parser import parse as dateutil_parse - -__all__ = [ - 'cached_property', - 'datetime_format', - 'datetime_parse', - 'fold_keys', -] - - -def cached_property(fn): - """Decorate property to cache return value.""" - return property(functools.lru_cache(maxsize=8)(fn)) - - -class ConfigDict(collections.UserDict): - """Silently ignore None values, only take key once.""" - - def __init__(self, **kwargs): - """Construct dictionary.""" - super().__init__(kwargs) - - def __setitem__(self, key, value): - """Store unique, not None values.""" - if value is None: - return - - if super().__contains__(key): - return - - super().__setitem__(key, value) - - -class FoldedString(collections.UserString): - """Foldcase sequences value.""" - - def __init__(self, seq): - super().__init__(seq) - self.data.casefold() - - -def fold_keys(): # noqa: D202 - """Fold case of dictionary keys.""" - - @functools.wraps(fold_keys) - def wrapped(mapping): - """Fold case of dictionary keys.""" - return {k.casefold(): v for (k, v) in mapping.items()} - - return wrapped - - -def datetime_parse(string): - """Convert timestamps to datetime. - - tzinfo aware, if provided. - """ - return dateutil_parse(string.upper(), fuzzy=True) - - -def datetime_format(dt): - """Format datetime in consistent style.""" - if isinstance(dt, str): - return datetime_parse(dt).isoformat() - - if isinstance(dt, datetime.datetime): - return dt.isoformat() - - raise ValueError('Unable to format {}. Type {} not supported.'.format( - dt, type(dt))) diff --git a/contrib/python/podman/podman/libs/_containers_attach.py b/contrib/python/podman/podman/libs/_containers_attach.py deleted file mode 100644 index 94247d349..000000000 --- a/contrib/python/podman/podman/libs/_containers_attach.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Exported method Container.attach().""" - -import collections -import fcntl -import logging -import struct -import sys -import termios - - -class Mixin: - """Publish attach() for inclusion in Container class.""" - - def attach(self, eot=4, stdin=None, stdout=None): - """Attach to container's PID1 stdin and stdout. - - stderr is ignored. - PseudoTTY work is done in start(). - """ - if stdin is None: - stdin = sys.stdin.fileno() - elif hasattr(stdin, 'fileno'): - stdin = stdin.fileno() - - if stdout is None: - stdout = sys.stdout.fileno() - elif hasattr(stdout, 'fileno'): - stdout = stdout.fileno() - - with self._client() as podman: - attach = podman.GetAttachSockets(self._id) - - # This is the UDS where all the IO goes - io_socket = attach['sockets']['io_socket'] - assert len(io_socket) <= 107,\ - 'Path length for sockets too long. {} > 107'.format( - len(io_socket) - ) - - # This is the control socket where resizing events are sent to conmon - # attach['sockets']['control_socket'] - self.pseudo_tty = collections.namedtuple( - 'PseudoTTY', - ['stdin', 'stdout', 'io_socket', 'control_socket', 'eot'])( - stdin, - stdout, - attach['sockets']['io_socket'], - attach['sockets']['control_socket'], - eot, - ) - - @property - def resize_handler(self): - """Send the new window size to conmon.""" - - def wrapped(signum, frame): # pylint: disable=unused-argument - packed = fcntl.ioctl(self.pseudo_tty.stdout, termios.TIOCGWINSZ, - struct.pack('HHHH', 0, 0, 0, 0)) - rows, cols, _, _ = struct.unpack('HHHH', packed) - logging.debug('Resize window(%dx%d) using %s', rows, cols, - self.pseudo_tty.control_socket) - - # TODO: Need some kind of timeout in case pipe is blocked - with open(self.pseudo_tty.control_socket, 'w') as skt: - # send conmon window resize message - skt.write('1 {} {}\n'.format(rows, cols)) - - return wrapped - - @property - def log_handler(self): - """Send command to reopen log to conmon.""" - - def wrapped(signum, frame): # pylint: disable=unused-argument - with open(self.pseudo_tty.control_socket, 'w') as skt: - # send conmon reopen log message - skt.write('2\n') - - return wrapped diff --git a/contrib/python/podman/podman/libs/_containers_start.py b/contrib/python/podman/podman/libs/_containers_start.py deleted file mode 100644 index 20130f5d6..000000000 --- a/contrib/python/podman/podman/libs/_containers_start.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Exported method Container.start().""" -import logging -import os -import select -import signal -import socket -import sys -import termios -import tty - -CONMON_BUFSZ = 8192 - - -class Mixin: - """Publish start() for inclusion in Container class.""" - - def start(self): - """Start container, return container on success. - - Will block if container has been detached. - """ - with self._client() as podman: - logging.debug('Starting Container "%s"', self._id) - results = podman.StartContainer(self._id) - logging.debug('Started Container "%s"', results['container']) - - if not hasattr(self, 'pseudo_tty') or self.pseudo_tty is None: - return self._refresh(podman) - - logging.debug('Setting up PseudoTTY for Container "%s"', - results['container']) - - try: - # save off the old settings for terminal - tcoldattr = termios.tcgetattr(self.pseudo_tty.stdin) - tty.setraw(self.pseudo_tty.stdin) - - # initialize container's window size - self.resize_handler(None, sys._getframe(0)) - - # catch any resizing events and send the resize info - # to the control fifo "socket" - signal.signal(signal.SIGWINCH, self.resize_handler) - - except termios.error: - tcoldattr = None - - try: - # TODO: Is socket.SOCK_SEQPACKET supported in Windows? - with socket.socket(socket.AF_UNIX, - socket.SOCK_SEQPACKET) as skt: - # Prepare socket for use with conmon/container - skt.connect(self.pseudo_tty.io_socket) - - sources = [skt, self.pseudo_tty.stdin] - while sources: - logging.debug('Waiting on sources: %s', sources) - readable, _, _ = select.select(sources, [], []) - - if skt in readable: - data = skt.recv(CONMON_BUFSZ) - if data: - # Remove source marker when writing - os.write(self.pseudo_tty.stdout, data[1:]) - else: - sources.remove(skt) - - if self.pseudo_tty.stdin in readable: - data = os.read(self.pseudo_tty.stdin, CONMON_BUFSZ) - if data: - skt.sendall(data) - - if self.pseudo_tty.eot in data: - sources.clear() - else: - sources.remove(self.pseudo_tty.stdin) - finally: - if tcoldattr: - termios.tcsetattr(self.pseudo_tty.stdin, termios.TCSADRAIN, - tcoldattr) - signal.signal(signal.SIGWINCH, signal.SIG_DFL) - return self._refresh(podman) diff --git a/contrib/python/podman/podman/libs/containers.py b/contrib/python/podman/podman/libs/containers.py deleted file mode 100644 index 7adecea8f..000000000 --- a/contrib/python/podman/podman/libs/containers.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Models for manipulating containers and storage.""" -import collections -import getpass -import json -import logging -import signal -import time - -from . import fold_keys -from ._containers_attach import Mixin as AttachMixin -from ._containers_start import Mixin as StartMixin - - -class Container(AttachMixin, StartMixin, collections.UserDict): - """Model for a container.""" - - def __init__(self, client, ident, data, refresh=True): - """Construct Container Model.""" - super(Container, self).__init__(data) - self._client = client - self._id = ident - - if refresh: - with client() as podman: - self._refresh(podman) - else: - for k, v in self.data.items(): - setattr(self, k, v) - if 'containerrunning' in self.data: - setattr(self, 'running', self.data['containerrunning']) - self.data['running'] = self.data['containerrunning'] - - assert self._id == data['id'],\ - 'Requested container id({}) does not match store id({})'.format( - self._id, data['id'] - ) - - def _refresh(self, podman, tries=1): - try: - ctnr = podman.GetContainer(self._id) - except BrokenPipeError: - logging.debug('Failed GetContainer(%s) try %d/3', self._id, tries) - if tries > 3: - raise - else: - with self._client() as pman: - self._refresh(pman, tries + 1) - else: - super().update(ctnr['container']) - - for k, v in self.data.items(): - setattr(self, k, v) - if 'containerrunning' in self.data: - setattr(self, 'running', self.data['containerrunning']) - self.data['running'] = self.data['containerrunning'] - - return self - - def refresh(self): - """Refresh status fields for this container.""" - with self._client() as podman: - return self._refresh(podman) - - def processes(self): - """Show processes running in container.""" - with self._client() as podman: - results = podman.ListContainerProcesses(self._id) - yield from results['container'] - - def changes(self): - """Retrieve container changes.""" - with self._client() as podman: - results = podman.ListContainerChanges(self._id) - return results['container'] - - def kill(self, sig=signal.SIGTERM, wait=25): - """Send signal to container. - - default signal is signal.SIGTERM. - wait n of seconds, 0 waits forever. - """ - with self._client() as podman: - podman.KillContainer(self._id, sig) - timeout = time.time() + wait - while True: - self._refresh(podman) - if self.status != 'running': # pylint: disable=no-member - return self - - if wait and timeout < time.time(): - raise TimeoutError() - - time.sleep(0.5) - - def inspect(self): - """Retrieve details about containers.""" - with self._client() as podman: - results = podman.InspectContainer(self._id) - obj = json.loads(results['container'], object_hook=fold_keys()) - return collections.namedtuple('ContainerInspect', obj.keys())(**obj) - - def export(self, target): - """Export container from store to tarball. - - TODO: should there be a compress option, like images? - """ - with self._client() as podman: - results = podman.ExportContainer(self._id, target) - return results['tarfile'] - - def commit(self, image_name, **kwargs): - """Create image from container. - - Keyword arguments: - author -- change image's author - message -- change image's message, docker format only. - pause -- pause container during commit - change -- Additional properties to change - - Change examples: - CMD=/usr/bin/zsh - ENTRYPOINT=/bin/sh date - ENV=TEST=test_containers.TestContainers.test_commit - EXPOSE=8888/tcp - LABEL=unittest=test_commit - USER=bozo:circus - VOLUME=/data - WORKDIR=/data/application - - All changes overwrite existing values. - See inspect() to obtain current settings. - """ - author = kwargs.get('author', None) or getpass.getuser() - change = kwargs.get('change', None) or [] - message = kwargs.get('message', None) or '' - pause = kwargs.get('pause', None) or True - - for c in change: - if c.startswith('LABEL=') and c.count('=') < 2: - raise ValueError( - 'LABEL should have the format: LABEL=label=value, not {}'. - format(c)) - - with self._client() as podman: - results = podman.Commit(self._id, image_name, change, author, - message, pause) - return results['image'] - - def stop(self, timeout=25): - """Stop container, return id on success.""" - with self._client() as podman: - podman.StopContainer(self._id, timeout) - return self._refresh(podman) - - def remove(self, force=False): - """Remove container, return id on success. - - force=True, stop running container. - """ - with self._client() as podman: - results = podman.RemoveContainer(self._id, force) - return results['container'] - - def restart(self, timeout=25): - """Restart container with timeout, return id on success.""" - with self._client() as podman: - podman.RestartContainer(self._id, timeout) - return self._refresh(podman) - - def rename(self, target): # pylint: disable=unused-argument - """Rename container, return id on success.""" - with self._client() as podman: - # TODO: Need arguments - results = podman.RenameContainer() - # TODO: fixup objects cached information - return results['container'] - - def resize_tty(self, width, height): # pylint: disable=unused-argument - """Resize container tty.""" - with self._client() as podman: - # TODO: magic re: attach(), arguments - podman.ResizeContainerTty() - - def pause(self): - """Pause container, return id on success.""" - with self._client() as podman: - podman.PauseContainer(self._id) - return self._refresh(podman) - - def unpause(self): - """Unpause container, return id on success.""" - with self._client() as podman: - podman.UnpauseContainer(self._id) - return self._refresh(podman) - - def update_container(self, *args, **kwargs): \ - # pylint: disable=unused-argument - """TODO: Update container..., return id on success.""" - with self._client() as podman: - podman.UpdateContainer() - return self._refresh(podman) - - def wait(self): - """Wait for container to finish, return 'returncode'.""" - with self._client() as podman: - results = podman.WaitContainer(self._id) - return int(results['exitcode']) - - def stats(self): - """Retrieve resource stats from the container.""" - with self._client() as podman: - results = podman.GetContainerStats(self._id) - obj = results['container'] - return collections.namedtuple('StatDetail', obj.keys())(**obj) - - def logs(self, *args, **kwargs): # pylint: disable=unused-argument - """Retrieve container logs.""" - with self._client() as podman: - results = podman.GetContainerLogs(self._id) - yield from results['container'] - - -class Containers(): - """Model for Containers collection.""" - - def __init__(self, client): - """Construct model for Containers collection.""" - self._client = client - - def list(self): - """List of containers in the container store.""" - with self._client() as podman: - results = podman.ListContainers() - for cntr in results['containers']: - yield Container(self._client, cntr['id'], cntr, refresh=False) - - def delete_stopped(self): - """Delete all stopped containers.""" - with self._client() as podman: - results = podman.DeleteStoppedContainers() - return results['containers'] - - def get(self, id_): - """Retrieve container details from store.""" - with self._client() as podman: - cntr = podman.GetContainer(id_) - return Container(self._client, cntr['container']['id'], - cntr['container']) diff --git a/contrib/python/podman/podman/libs/errors.py b/contrib/python/podman/podman/libs/errors.py deleted file mode 100644 index 2821d3597..000000000 --- a/contrib/python/podman/podman/libs/errors.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Error classes and wrappers for VarlinkError.""" -from varlink import VarlinkError - - -class VarlinkErrorProxy(VarlinkError): - """Class to Proxy VarlinkError methods.""" - - def __init__(self, message, namespaced=False): - """Construct proxy from Exception.""" - super().__init__(message.as_dict(), namespaced) - self._message = message - self.__module__ = 'libpod' - - def __getattr__(self, method): - """Return attribute from proxied Exception.""" - if hasattr(self._message, method): - return getattr(self._message, method) - - try: - return self._message.parameters()[method] - except KeyError: - raise AttributeError('No such attribute: {}'.format(method)) - - -class ContainerNotFound(VarlinkErrorProxy): - """Raised when Client cannot find requested container.""" - - -class ImageNotFound(VarlinkErrorProxy): - """Raised when Client cannot find requested image.""" - - -class PodNotFound(VarlinkErrorProxy): - """Raised when Client cannot find requested image.""" - - -class PodContainerError(VarlinkErrorProxy): - """Raised when a container fails requested pod operation.""" - - -class NoContainerRunning(VarlinkErrorProxy): - """Raised when no container is running in pod.""" - - -class NoContainersInPod(VarlinkErrorProxy): - """Raised when Client fails to connect to runtime.""" - - -class ErrorOccurred(VarlinkErrorProxy): - """Raised when an error occurs during the execution. - - See error() to see actual error text. - """ - - -class PodmanError(VarlinkErrorProxy): - """Raised when Client fails to connect to runtime.""" - - -ERROR_MAP = { - 'io.podman.ContainerNotFound': ContainerNotFound, - 'io.podman.ErrorOccurred': ErrorOccurred, - 'io.podman.ImageNotFound': ImageNotFound, - 'io.podman.NoContainerRunning': NoContainerRunning, - 'io.podman.NoContainersInPod': NoContainersInPod, - 'io.podman.PodContainerError': PodContainerError, - 'io.podman.PodNotFound': PodNotFound, - 'io.podman.RuntimeError': PodmanError, -} - - -def error_factory(exception): - """Map Exceptions to a discrete type.""" - try: - return ERROR_MAP[exception.error()](exception) - except KeyError: - return exception diff --git a/contrib/python/podman/podman/libs/images.py b/contrib/python/podman/podman/libs/images.py deleted file mode 100644 index 29ebe2c0f..000000000 --- a/contrib/python/podman/podman/libs/images.py +++ /dev/null @@ -1,164 +0,0 @@ -"""Models for manipulating images in/to/from storage.""" -import collections -import copy -import json -import logging - -from . import ConfigDict, fold_keys -from .containers import Container - - -class Image(collections.UserDict): - """Model for an Image.""" - - def __init__(self, client, id, data): - """Construct Image Model.""" - super().__init__(data) - for k, v in data.items(): - setattr(self, k, v) - - self._id = id - self._client = client - - assert self._id == data['id'],\ - 'Requested image id({}) does not match store id({})'.format( - self._id, data['id'] - ) - - @staticmethod - def _split_token(values=None, sep='='): - if not values: - return {} - return { - k: v1 for k, v1 in (v0.split(sep, 1) for v0 in values) - } - - def create(self, *args, **kwargs): - """Create container from image. - - Pulls defaults from image.inspect() - """ - details = self.inspect() - - config = ConfigDict(image_id=self._id, **kwargs) - config['command'] = details.config.get('cmd') - config['env'] = self._split_token(details.config.get('env')) - config['image'] = copy.deepcopy(details.repotags[0]) - config['labels'] = copy.deepcopy(details.labels) - config['net_mode'] = 'bridge' - config['network'] = 'bridge' - - logging.debug('Image %s: create config: %s', self._id, config) - with self._client() as podman: - id_ = podman.CreateContainer(config)['container'] - cntr = podman.GetContainer(id_) - return Container(self._client, id_, cntr['container']) - - container = create - - def export(self, dest, compressed=False): - """Write image to dest, return id on success.""" - with self._client() as podman: - results = podman.ExportImage(self._id, dest, compressed) - return results['image'] - - def history(self): - """Retrieve image history.""" - with self._client() as podman: - for r in podman.HistoryImage(self._id)['history']: - yield collections.namedtuple('HistoryDetail', r.keys())(**r) - - def inspect(self): - """Retrieve details about image.""" - with self._client() as podman: - results = podman.InspectImage(self._id) - obj = json.loads(results['image'], object_hook=fold_keys()) - return collections.namedtuple('ImageInspect', obj.keys())(**obj) - - def push(self, target, tlsverify=True): - """Copy image to target, return id on success.""" - with self._client() as podman: - results = podman.PushImage(self._id, target, tlsverify) - return results['image'] - - def remove(self, force=False): - """Delete image, return id on success. - - force=True, stop any running containers using image. - """ - with self._client() as podman: - results = podman.RemoveImage(self._id, force) - return results['image'] - - def tag(self, tag): - """Tag image.""" - with self._client() as podman: - results = podman.TagImage(self._id, tag) - return results['image'] - - -class Images(): - """Model for Images collection.""" - - def __init__(self, client): - """Construct model for Images collection.""" - self._client = client - - def list(self): - """List all images in the libpod image store.""" - with self._client() as podman: - results = podman.ListImages() - for img in results['images']: - yield Image(self._client, img['id'], img) - - def build(self, dockerfile=None, tags=None, **kwargs): - """Build container from image. - - See podman-build.1.md for kwargs details. - """ - if dockerfile is None: - raise ValueError('"dockerfile" is a required argument.') - elif not hasattr(dockerfile, '__iter__'): - raise ValueError('"dockerfile" is required to be an iter.') - - if tags is None: - raise ValueError('"tags" is a required argument.') - elif not hasattr(tags, '__iter__'): - raise ValueError('"tags" is required to be an iter.') - - config = ConfigDict(dockerfile=dockerfile, tags=tags, **kwargs) - with self._client() as podman: - result = podman.BuildImage(config) - return self.get(result['image']['id']), \ - (line for line in result['image']['logs']) - - def delete_unused(self): - """Delete Images not associated with a container.""" - with self._client() as podman: - results = podman.DeleteUnusedImages() - return results['images'] - - def import_image(self, source, reference, message='', changes=None): - """Read image tarball from source and save in image store.""" - with self._client() as podman: - results = podman.ImportImage(source, reference, message, changes) - return results['image'] - - def pull(self, source): - """Copy image from registry to image store.""" - with self._client() as podman: - results = podman.PullImage(source) - return results['id'] - - def search(self, id_, limit=25): - """Search registries for id.""" - with self._client() as podman: - results = podman.SearchImage(id_, limit) - for img in results['images']: - yield collections.namedtuple('ImageSearch', img.keys())(**img) - - def get(self, id_): - """Get Image from id.""" - with self._client() as podman: - result = podman.GetImage(id_) - return Image(self._client, result['image']['id'], result['image']) diff --git a/contrib/python/podman/podman/libs/pods.py b/contrib/python/podman/podman/libs/pods.py deleted file mode 100644 index 2a85f2624..000000000 --- a/contrib/python/podman/podman/libs/pods.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Model for accessing details of Pods from podman service.""" -import collections -import json -import signal -import time - -from . import ConfigDict, FoldedString, fold_keys - - -class Pod(collections.UserDict): - """Model for a Pod.""" - - def __init__(self, client, ident, data): - """Construct Pod model.""" - super().__init__(data) - - self._ident = ident - self._client = client - - with client() as podman: - self._refresh(podman) - - def _refresh(self, podman): - pod = podman.GetPod(self._ident) - super().update(pod['pod']) - - for k, v in self.data.items(): - setattr(self, k, v) - return self - - def inspect(self): - """Retrieve details about pod.""" - with self._client() as podman: - results = podman.InspectPod(self._ident) - obj = json.loads(results['pod'], object_hook=fold_keys()) - obj['id'] = obj['config']['id'] - return collections.namedtuple('PodInspect', obj.keys())(**obj) - - def kill(self, signal_=signal.SIGTERM, wait=25): - """Send signal to all containers in pod. - - default signal is signal.SIGTERM. - wait n of seconds, 0 waits forever. - """ - with self._client() as podman: - podman.KillPod(self._ident, signal_) - timeout = time.time() + wait - while True: - # pylint: disable=maybe-no-member - self._refresh(podman) - running = FoldedString(self.status) - if running != 'running': - break - - if wait and timeout < time.time(): - raise TimeoutError() - - time.sleep(0.5) - return self - - def pause(self): - """Pause all containers in the pod.""" - with self._client() as podman: - podman.PausePod(self._ident) - return self._refresh(podman) - - def refresh(self): - """Refresh status fields for this pod.""" - with self._client() as podman: - return self._refresh(podman) - - def remove(self, force=False): - """Remove pod and its containers returning pod ident. - - force=True, stop any running container. - """ - with self._client() as podman: - results = podman.RemovePod(self._ident, force) - return results['pod'] - - def restart(self): - """Restart all containers in the pod.""" - with self._client() as podman: - podman.RestartPod(self._ident) - return self._refresh(podman) - - def stats(self): - """Stats on all containers in the pod.""" - with self._client() as podman: - results = podman.GetPodStats(self._ident) - for obj in results['containers']: - yield collections.namedtuple('ContainerStats', obj.keys())(**obj) - - def start(self): - """Start all containers in the pod.""" - with self._client() as podman: - podman.StartPod(self._ident) - return self._refresh(podman) - - def stop(self): - """Stop all containers in the pod.""" - with self._client() as podman: - podman.StopPod(self._ident) - return self._refresh(podman) - - def top(self): - """Display stats for all containers.""" - with self._client() as podman: - results = podman.TopPod(self._ident) - return results['pod'] - - def unpause(self): - """Unpause all containers in the pod.""" - with self._client() as podman: - podman.UnpausePod(self._ident) - return self._refresh(podman) - - def wait(self): - """Wait for all containers to exit.""" - with self._client() as podman: - results = podman.WaitPod(self._ident) - return results['pod'] - - -class Pods(): - """Model for accessing pods.""" - - def __init__(self, client): - """Construct pod model.""" - self._client = client - - def create(self, - ident=None, - cgroupparent=None, - labels=None, - share=None, - infra=False): - """Create a new empty pod.""" - config = ConfigDict( - name=ident, - cgroupParent=cgroupparent, - labels=labels, - share=share, - infra=infra, - ) - - with self._client() as podman: - result = podman.CreatePod(config) - details = podman.GetPod(result['pod']) - return Pod(self._client, result['pod'], details['pod']) - - def get(self, ident): - """Get Pod from ident.""" - with self._client() as podman: - result = podman.GetPod(ident) - return Pod(self._client, result['pod']['id'], result['pod']) - - def list(self): - """List all pods.""" - with self._client() as podman: - results = podman.ListPods() - for pod in results['pods']: - yield Pod(self._client, pod['id'], pod) diff --git a/contrib/python/podman/podman/libs/system.py b/contrib/python/podman/podman/libs/system.py deleted file mode 100644 index c611341e4..000000000 --- a/contrib/python/podman/podman/libs/system.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Models for accessing details from varlink server.""" -import collections - -import pkg_resources - -from . import cached_property - - -class System(): - """Model for accessing system resources.""" - - def __init__(self, client): - """Construct system model.""" - self._client = client - - @cached_property - def versions(self): - """Access versions.""" - with self._client() as podman: - vers = podman.GetVersion()['version'] - - client = '0.0.0' - try: - client = pkg_resources.get_distribution('podman').version - except Exception: # pylint: disable=broad-except - pass - vers['client_version'] = client - return collections.namedtuple('Version', vers.keys())(**vers) - - def info(self): - """Return podman info.""" - with self._client() as podman: - info = podman.GetInfo()['info'] - return collections.namedtuple('Info', info.keys())(**info) - - def ping(self): - """Return True if server awake.""" - with self._client() as podman: - response = podman.Ping() - return response['ping']['message'] == 'OK' diff --git a/contrib/python/podman/podman/libs/tunnel.py b/contrib/python/podman/podman/libs/tunnel.py deleted file mode 100644 index ac1dff594..000000000 --- a/contrib/python/podman/podman/libs/tunnel.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Cache for SSH tunnels.""" -import collections -import getpass -import logging -import os -import subprocess -import threading -import time -import weakref -from contextlib import suppress - -import psutil - -Context = collections.namedtuple('Context', ( - 'uri', - 'interface', - 'local_socket', - 'remote_socket', - 'username', - 'hostname', - 'port', - 'identity_file', -)) -Context.__new__.__defaults__ = (None, ) * len(Context._fields) - - -class Portal(collections.MutableMapping): - """Expiring container for tunnels.""" - - def __init__(self, sweap=25): - """Construct portal, reap tunnels every sweap seconds.""" - self.data = collections.OrderedDict() - self.sweap = sweap - self.ttl = sweap * 2 - self.lock = threading.RLock() - self._schedule_reaper() - - def __getitem__(self, key): - """Given uri return tunnel and update TTL.""" - with self.lock: - value, _ = self.data[key] - self.data[key] = (value, time.time() + self.ttl) - self.data.move_to_end(key) - return value - - def __setitem__(self, key, value): - """Store given tunnel keyed with uri.""" - if not isinstance(value, Tunnel): - raise ValueError('Portals only support Tunnels.') - - with self.lock: - self.data[key] = (value, time.time() + self.ttl) - self.data.move_to_end(key) - - def __delitem__(self, key): - """Remove and close tunnel from portal.""" - with self.lock: - value, _ = self.data[key] - del self.data[key] - value.close() - del value - - def __iter__(self): - """Iterate tunnels.""" - with self.lock: - values = self.data.values() - - for tunnel, _ in values: - yield tunnel - - def __len__(self): - """Return number of tunnels in portal.""" - with self.lock: - return len(self.data) - - def _schedule_reaper(self): - timer = threading.Timer(interval=self.sweap, function=self.reap) - timer.setName('PortalReaper') - timer.setDaemon(True) - timer.start() - - def reap(self): - """Remove tunnels who's TTL has expired.""" - now = time.time() - with self.lock: - reaped_data = self.data.copy() - for entry in reaped_data.items(): - if entry[1][1] < now: - del self.data[entry[0]] - else: - # StopIteration as soon as possible - break - self._schedule_reaper() - - -class Tunnel(): - """SSH tunnel.""" - - def __init__(self, context): - """Construct Tunnel.""" - self.context = context - self._closed = True - - @property - def closed(self): - """Is tunnel closed.""" - return self._closed - - def bore(self): - """Create SSH tunnel from given context.""" - cmd = ['ssh', '-fNT'] - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - cmd.append('-v') - else: - cmd.append('-q') - - if self.context.port: - cmd.extend(('-p', str(self.context.port))) - - cmd.extend(('-L', '{}:{}'.format(self.context.local_socket, - self.context.remote_socket))) - if self.context.identity_file: - cmd.extend(('-i', self.context.identity_file)) - - cmd.append('{}@{}'.format(self.context.username, - self.context.hostname)) - - logging.debug('Opening tunnel "%s", cmd "%s"', self.context.uri, - ' '.join(cmd)) - - tunnel = subprocess.Popen(cmd, close_fds=True) - # The return value of Popen() has no long term value as that process - # has already exited by the time control is returned here. This is a - # side effect of the -f option. wait() will be called to clean up - # resources. - for _ in range(300): - # TODO: Make timeout configurable - if os.path.exists(self.context.local_socket) \ - or tunnel.returncode is not None: - break - with suppress(subprocess.TimeoutExpired): - # waiting for either socket to be created - # or first child to exit - tunnel.wait(0.5) - else: - raise TimeoutError( - 'Failed to create tunnel "{}", using: "{}"'.format( - self.context.uri, ' '.join(cmd))) - if tunnel.returncode is not None and tunnel.returncode != 0: - raise subprocess.CalledProcessError(tunnel.returncode, - ' '.join(cmd)) - tunnel.wait() - - self._closed = False - weakref.finalize(self, self.close) - return self - - def close(self): - """Close SSH tunnel.""" - logging.debug('Closing tunnel "%s"', self.context.uri) - - if self._closed: - return - - # Find all ssh instances for user with uri tunnel the hard way... - targets = [ - p - for p in psutil.process_iter(attrs=['name', 'username', 'cmdline']) - if p.info['username'] == getpass.getuser() - and p.info['name'] == 'ssh' - and self.context.local_socket in ' '.join(p.info['cmdline']) - ] # yapf: disable - - # ask nicely for ssh to quit, reap results - for proc in targets: - proc.terminate() - _, alive = psutil.wait_procs(targets, timeout=300) - - # kill off the uncooperative, then report any stragglers - for proc in alive: - proc.kill() - _, alive = psutil.wait_procs(targets, timeout=300) - - for proc in alive: - logging.info('process %d survived SIGKILL, giving up.', proc.pid) - - with suppress(OSError): - os.remove(self.context.local_socket) - self._closed = True diff --git a/contrib/python/podman/requirements.txt b/contrib/python/podman/requirements.txt deleted file mode 100644 index 1caf5e286..000000000 --- a/contrib/python/podman/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -psutil -python-dateutil -setuptools>=39 -varlink diff --git a/contrib/python/podman/setup.py b/contrib/python/podman/setup.py deleted file mode 100755 index 9d54bb3ac..000000000 --- a/contrib/python/podman/setup.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -import os - -from setuptools import find_packages, setup - - -root = os.path.abspath(os.path.dirname(__file__)) - -with open(os.path.join(root, 'README.md')) as me: - readme = me.read() - -with open(os.path.join(root, 'requirements.txt')) as r: - requirements = r.read().splitlines() - - -setup( - name='podman', - version=os.environ.get('PODMAN_VERSION', '0.0.0'), - description='A library for communicating with a Podman server', - author='Jhon Honce', - author_email='jhonce@redhat.com', - license='Apache Software License', - long_description=readme, - include_package_data=True, - install_requires=requirements, - packages=find_packages(exclude=['test']), - python_requires='>=3', - zip_safe=True, - url='http://github.com/containers/libpod', - keywords='varlink libpod podman', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.4', - 'Topic :: Software Development', - ]) diff --git a/contrib/python/podman/test/__init__.py b/contrib/python/podman/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/contrib/python/podman/test/__init__.py +++ /dev/null diff --git a/contrib/python/podman/test/podman_testcase.py b/contrib/python/podman/test/podman_testcase.py deleted file mode 100644 index da73c1024..000000000 --- a/contrib/python/podman/test/podman_testcase.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Base for podman tests.""" -import contextlib -import functools -import itertools -import os -import subprocess -import time -import unittest - -from varlink import VarlinkError - -MethodNotImplemented = 'org.varlink.service.MethodNotImplemented' - - -class PodmanTestCase(unittest.TestCase): - """Hide the sausage making of initializing storage.""" - - @classmethod - def setUpClass(cls): - """Fixture to setup podman test case.""" - if hasattr(PodmanTestCase, 'alpine_process'): - PodmanTestCase.tearDownClass() - - def run_cmd(*args): - cmd = list(itertools.chain(*args)) - try: - pid = subprocess.Popen( - cmd, - close_fds=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = pid.communicate() - except OSError as e: - print('{}: {}({})'.format(cmd, e.strerror, e.returncode)) - except ValueError as e: - print('{}: {}'.format(cmd, e.message)) - raise - else: - return out.strip() - - tmpdir = os.environ.get('TMPDIR', '/tmp') - podman_args = [ - '--storage-driver=vfs', - '--cgroup-manager=cgroupfs', - '--root={}/crio'.format(tmpdir), - '--runroot={}/crio-run'.format(tmpdir), - '--cni-config-dir={}/cni/net.d'.format(tmpdir), - ] - - run_podman = functools.partial(run_cmd, ['podman'], podman_args) - - id = run_podman(['pull', 'alpine']) - setattr(PodmanTestCase, 'alpine_id', id) - - run_podman(['pull', 'busybox']) - run_podman(['images']) - - run_cmd(['rm', '-f', '{}/alpine_gold.tar'.format(tmpdir)]) - run_podman([ - 'save', '--output', '{}/alpine_gold.tar'.format(tmpdir), 'alpine' - ]) - - PodmanTestCase.alpine_log = open( - os.path.join('/tmp/', 'alpine.log'), 'w') - - cmd = ['podman'] - cmd.extend(podman_args) - # cmd.extend(['run', '-d', 'alpine', 'sleep', '500']) - cmd.extend(['run', '-dt', 'alpine', '/bin/sh']) - PodmanTestCase.alpine_process = subprocess.Popen( - cmd, - stdout=PodmanTestCase.alpine_log, - stderr=subprocess.STDOUT, - ) - - PodmanTestCase.busybox_log = open( - os.path.join('/tmp/', 'busybox.log'), 'w') - - cmd = ['podman'] - cmd.extend(podman_args) - cmd.extend(['create', 'busybox']) - PodmanTestCase.busybox_process = subprocess.Popen( - cmd, - stdout=PodmanTestCase.busybox_log, - stderr=subprocess.STDOUT, - ) - # give podman time to start ctnr - time.sleep(2) - - # Close our handle of file - PodmanTestCase.alpine_log.close() - PodmanTestCase.busybox_log.close() - - @classmethod - def tearDownClass(cls): - """Fixture to clean up after podman unittest.""" - try: - PodmanTestCase.alpine_process.kill() - assert 0 == PodmanTestCase.alpine_process.wait(500) - delattr(PodmanTestCase, 'alpine_process') - - PodmanTestCase.busybox_process.kill() - assert 0 == PodmanTestCase.busybox_process.wait(500) - except Exception as e: - print('Exception: {}'.format(e)) - raise - - @contextlib.contextmanager - def assertRaisesNotImplemented(self): - """Sugar for unimplemented varlink methods.""" - with self.assertRaisesRegex(VarlinkError, MethodNotImplemented): - yield diff --git a/contrib/python/podman/test/retry_decorator.py b/contrib/python/podman/test/retry_decorator.py deleted file mode 100644 index 31e06f382..000000000 --- a/contrib/python/podman/test/retry_decorator.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Decorator to retry failed method.""" -import functools -import time - - -def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, print_=None): - """Retry calling the decorated function using an exponential backoff. - - Specialized for our unittests - from: - http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ - original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry - - :param ExceptionToCheck: the exception to check. may be a tuple of - exceptions to check - :type ExceptionToCheck: Exception or tuple - :param tries: number of times to try (not retry) before giving up - :type tries: int - :param delay: initial delay between retries in seconds - :type delay: int - :param backoff: backoff multiplier e.g. value of 2 will double the delay - each retry - :type backoff: int - """ - def deco_retry(f): - @functools.wraps(f) - def f_retry(*args, **kwargs): - mtries, mdelay = tries, delay - while mtries > 1: - try: - return f(*args, **kwargs) - except ExceptionToCheck as e: - if print_: - print_('{}, Retrying in {} seconds...'.format( - str(e), mdelay)) - time.sleep(mdelay) - mtries -= 1 - mdelay *= backoff - return f(*args, **kwargs) - - return f_retry # true decorator - - return deco_retry diff --git a/contrib/python/podman/test/test_client.py b/contrib/python/podman/test/test_client.py deleted file mode 100644 index 3fc6d39dc..000000000 --- a/contrib/python/podman/test/test_client.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import absolute_import - -import unittest -from unittest.mock import patch - -import podman -from podman.client import BaseClient, Client, LocalClient, RemoteClient - - -class TestClient(unittest.TestCase): - def setUp(self): - pass - - @patch('podman.libs.system.System.ping', return_value=True) - def test_local(self, mock_ping): - p = Client( - uri='unix:/run/podman', - interface='io.podman', - ) - - self.assertIsInstance(p._client, LocalClient) - self.assertIsInstance(p._client, BaseClient) - - mock_ping.assert_called_once_with() - - @patch('podman.libs.system.System.ping', return_value=True) - def test_remote(self, mock_ping): - p = Client( - uri='unix:/run/podman', - interface='io.podman', - remote_uri='ssh://user@hostname/run/podman/podman', - identity_file='~/.ssh/id_rsa') - - self.assertIsInstance(p._client, BaseClient) - mock_ping.assert_called_once_with() diff --git a/contrib/python/podman/test/test_containers.py b/contrib/python/podman/test/test_containers.py deleted file mode 100644 index 5201956e8..000000000 --- a/contrib/python/podman/test/test_containers.py +++ /dev/null @@ -1,244 +0,0 @@ -import os -import signal -import unittest -from pathlib import Path -from test.podman_testcase import PodmanTestCase -from test.retry_decorator import retry - -import podman - - -class TestContainers(PodmanTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def setUp(self): - self.tmpdir = os.environ['TMPDIR'] - self.host = os.environ['PODMAN_HOST'] - - self.pclient = podman.Client(self.host) - self.loadCache() - - def tearDown(self): - pass - - def loadCache(self): - self.containers = list(self.pclient.containers.list()) - - self.alpine_ctnr = next( - iter([c for c in self.containers if 'alpine' in c['image']] or []), - None) - - if self.alpine_ctnr and self.alpine_ctnr.status != 'running': - self.alpine_ctnr.start() - - def test_list(self): - self.assertGreaterEqual(len(self.containers), 2) - self.assertIsNotNone(self.alpine_ctnr) - self.assertIn('alpine', self.alpine_ctnr.image) - - def test_delete_stopped(self): - before = len(self.containers) - - self.alpine_ctnr.stop() - target = self.alpine_ctnr.id - actual = self.pclient.containers.delete_stopped() - self.assertIn(target, actual) - - self.loadCache() - after = len(self.containers) - - self.assertLess(after, before) - TestContainers.setUpClass() - - def test_get(self): - actual = self.pclient.containers.get(self.alpine_ctnr.id) - for k in ['id', 'status', 'ports']: - self.assertEqual(actual[k], self.alpine_ctnr[k]) - - with self.assertRaises(podman.ContainerNotFound): - self.pclient.containers.get("bozo") - - def test_attach(self): - # StringIO does not support fileno() so we had to go old school - input = os.path.join(self.tmpdir, 'test_attach.stdin') - output = os.path.join(self.tmpdir, 'test_attach.stdout') - - with open(input, 'w+') as mock_in, open(output, 'w+') as mock_out: - # double quote is indeed in the expected place - mock_in.write('echo H"ello, World"; exit\n') - mock_in.seek(0, 0) - - ctnr = self.pclient.images.get(self.alpine_ctnr.image).container( - detach=True, tty=True) - ctnr.attach(stdin=mock_in.fileno(), stdout=mock_out.fileno()) - ctnr.start() - - mock_out.flush() - mock_out.seek(0, 0) - output = mock_out.read() - self.assertIn('Hello', output) - - ctnr.remove(force=True) - - def test_processes(self): - actual = list(self.alpine_ctnr.processes()) - self.assertGreaterEqual(len(actual), 2) - - def test_start_stop_wait(self): - ctnr = self.alpine_ctnr.stop() - self.assertFalse(ctnr['running']) - - ctnr.start() - self.assertTrue(ctnr.running) - - ctnr.stop() - self.assertFalse(ctnr['containerrunning']) - - actual = ctnr.wait() - self.assertGreaterEqual(actual, 0) - - def test_changes(self): - actual = self.alpine_ctnr.changes() - - self.assertListEqual( - sorted(['changed', 'added', 'deleted']), sorted( - list(actual.keys()))) - - # TODO: brittle, depends on knowing history of ctnr - self.assertGreaterEqual(len(actual['changed']), 0) - self.assertGreaterEqual(len(actual['added']), 0) - self.assertEqual(len(actual['deleted']), 0) - - def test_kill(self): - self.assertTrue(self.alpine_ctnr.running) - ctnr = self.alpine_ctnr.kill(signal.SIGKILL) - self.assertFalse(ctnr.running) - - def test_inspect(self): - actual = self.alpine_ctnr.inspect() - self.assertEqual(actual.id, self.alpine_ctnr.id) - # TODO: Datetime values from inspect missing offset in CI instance - # self.assertEqual( - # datetime_parse(actual.created), - # datetime_parse(self.alpine_ctnr.createdat)) - - def test_export(self): - target = os.path.join(self.tmpdir, 'alpine_export_ctnr.tar') - - actual = self.alpine_ctnr.export(target) - self.assertEqual(actual, target) - self.assertTrue(os.path.isfile(target)) - self.assertGreater(os.path.getsize(target), 0) - - def test_commit(self): - # TODO: Test for STOPSIGNAL when supported by OCI - # TODO: Test for message when supported by OCI - details = self.pclient.images.get(self.alpine_ctnr.image).inspect() - changes = ['ENV=' + i for i in details.config['env']] - changes.append('CMD=/usr/bin/zsh') - changes.append('ENTRYPOINT=/bin/sh date') - changes.append('ENV=TEST=test_containers.TestContainers.test_commit') - changes.append('EXPOSE=80') - changes.append('EXPOSE=8888') - changes.append('LABEL=unittest=test_commit') - changes.append('USER=bozo:circus') - changes.append('VOLUME=/data') - changes.append('WORKDIR=/data/application') - - id = self.alpine_ctnr.commit( - 'alpine3', author='Bozo the clown', change=changes, pause=True) - img = self.pclient.images.get(id) - self.assertIsNotNone(img) - - details = img.inspect() - self.assertEqual(details.author, 'Bozo the clown') - self.assertListEqual(['/usr/bin/zsh'], details.config['cmd']) - self.assertListEqual(['/bin/sh date'], - details.config['entrypoint']) - self.assertIn('TEST=test_containers.TestContainers.test_commit', - details.config['env']) - self.assertTrue( - [e for e in details.config['env'] if 'PATH=' in e]) - self.assertDictEqual({ - '80': {}, - '8888': {}, - }, details.config['exposedports']) - self.assertDictEqual({'unittest': 'test_commit'}, details.labels) - self.assertEqual('bozo:circus', details.config['user']) - self.assertEqual({'/data': {}}, details.config['volumes']) - self.assertEqual('/data/application', - details.config['workingdir']) - - def test_remove(self): - before = len(self.containers) - - with self.assertRaises(podman.ErrorOccurred): - self.alpine_ctnr.remove() - - self.assertEqual( - self.alpine_ctnr.id, self.alpine_ctnr.remove(force=True)) - self.loadCache() - after = len(self.containers) - - self.assertLess(after, before) - TestContainers.setUpClass() - - def test_restart(self): - self.assertTrue(self.alpine_ctnr.running) - before = self.alpine_ctnr.runningfor - - ctnr = self.alpine_ctnr.restart() - self.assertTrue(ctnr.running) - - after = self.alpine_ctnr.runningfor - - # TODO: restore check when restart zeros counter - # self.assertLess(after, before) - - def test_rename(self): - with self.assertRaisesNotImplemented(): - self.alpine_ctnr.rename('new_alpine') - - def test_resize_tty(self): - with self.assertRaisesNotImplemented(): - self.alpine_ctnr.resize_tty(132, 43) - - def test_pause_unpause(self): - self.assertTrue(self.alpine_ctnr.running) - - ctnr = self.alpine_ctnr.pause() - self.assertEqual(ctnr.status, 'paused') - - ctnr = self.alpine_ctnr.unpause() - self.assertTrue(ctnr.running) - self.assertTrue(ctnr.status, 'running') - - # creating cgoups can be flakey - @retry(podman.libs.errors.ErrorOccurred, tries=4, delay=2, print_=print) - def test_stats(self): - try: - self.assertTrue(self.alpine_ctnr.running) - - actual = self.alpine_ctnr.stats() - self.assertEqual(self.alpine_ctnr.id, actual.id) - self.assertEqual(self.alpine_ctnr.names, actual.name) - except Exception: - info = Path('/proc/self/mountinfo') - with info.open() as fd: - print('{} {}'.format(self.alpine_ctnr.id, info)) - print(fd.read()) - - def test_logs(self): - self.assertTrue(self.alpine_ctnr.running) - actual = list(self.alpine_ctnr.logs()) - self.assertIsNotNone(actual) - - -if __name__ == '__main__': - unittest.main() diff --git a/contrib/python/podman/test/test_images.py b/contrib/python/podman/test/test_images.py deleted file mode 100644 index af6d4741e..000000000 --- a/contrib/python/podman/test/test_images.py +++ /dev/null @@ -1,174 +0,0 @@ -import itertools -import os -import unittest -from collections import Counter -from datetime import datetime, timezone -from test.podman_testcase import PodmanTestCase - -import podman -from podman import FoldedString - - -class TestImages(PodmanTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def setUp(self): - self.tmpdir = os.environ['TMPDIR'] - self.host = os.environ['PODMAN_HOST'] - - self.pclient = podman.Client(self.host) - self.images = self.loadCache() - - def tearDown(self): - pass - - def loadCache(self): - with podman.Client(self.host) as pclient: - self.images = list(pclient.images.list()) - - self.alpine_image = next( - iter([ - i for i in self.images - if 'docker.io/library/alpine:latest' in i['repoTags'] - ] or []), None) - - return self.images - - def test_list(self): - actual = self.loadCache() - self.assertGreaterEqual(len(actual), 2) - self.assertIsNotNone(self.alpine_image) - - @unittest.skip('TODO: missing buildah json file') - def test_build(self): - path = os.path.join(self.tmpdir, 'ctnr', 'Dockerfile') - img, logs = self.pclient.images.build( - dockerfile=[path], - tags=['alpine-unittest'], - ) - self.assertIsNotNone(img) - self.assertIn('localhost/alpine-unittest:latest', img.repoTags) - self.assertLess( - podman.datetime_parse(img.created), datetime.now(timezone.utc)) - self.assertTrue(logs) - - def test_create(self): - img_details = self.alpine_image.inspect() - - actual = self.alpine_image.container(command=['sleep', '1h']) - self.assertIsNotNone(actual) - self.assertEqual(FoldedString(actual.status), 'configured') - - ctnr = actual.start() - self.assertEqual(FoldedString(ctnr.status), 'running') - - ctnr_details = ctnr.inspect() - for e in img_details.config['env']: - self.assertIn(e, ctnr_details.config['env']) - - def test_export(self): - path = os.path.join(self.tmpdir, 'alpine_export.tar') - target = 'oci-archive:{}:latest'.format(path) - - actual = self.alpine_image.export(target, False) - self.assertTrue(actual) - self.assertTrue(os.path.isfile(path)) - - def test_get(self): - actual = self.pclient.images.get(self.alpine_image.id) - self.assertEqual(actual, self.alpine_image) - - def test_history(self): - records = [] - bucket = Counter() - for record in self.alpine_image.history(): - self.assertIn(record.id, (self.alpine_image.id, '<missing>')) - bucket[record.id] += 1 - records.append(record) - - self.assertGreater(bucket[self.alpine_image.id], 0) - self.assertEqual(sum(bucket.values()), len(records)) - - def test_inspect(self): - actual = self.alpine_image.inspect() - self.assertEqual(actual.id, self.alpine_image.id) - - def test_push(self): - path = '{}/alpine_push'.format(self.tmpdir) - target = 'dir:{}'.format(path) - self.alpine_image.push(target, tlsverify=False) - - self.assertTrue(os.path.isfile(os.path.join(path, 'manifest.json'))) - self.assertTrue(os.path.isfile(os.path.join(path, 'version'))) - - def test_tag(self): - self.assertEqual(self.alpine_image.id, - self.alpine_image.tag('alpine:fubar')) - self.loadCache() - self.assertIn('localhost/alpine:fubar', self.alpine_image.repoTags) - - def test_remove(self): - before = self.loadCache() - - # assertRaises doesn't follow the import name :( - with self.assertRaises(podman.ErrorOccurred): - self.alpine_image.remove() - - actual = self.alpine_image.remove(force=True) - self.assertEqual(self.alpine_image.id, actual) - after = self.loadCache() - - self.assertLess(len(after), len(before)) - TestImages.setUpClass() - self.loadCache() - - def test_import_delete_unused(self): - before = self.loadCache() - # create unused image, so we have something to delete - source = os.path.join(self.tmpdir, 'alpine_gold.tar') - new_img = self.pclient.images.import_image( - source, - 'alpine2:latest', - 'unittest.test_import', - ) - after = self.loadCache() - - self.assertEqual(len(before) + 1, len(after)) - self.assertIsNotNone( - next(iter([i for i in after if new_img in i['id']] or []), None)) - - actual = self.pclient.images.delete_unused() - self.assertIn(new_img, actual) - - after = self.loadCache() - self.assertGreaterEqual(len(before), len(after)) - - TestImages.setUpClass() - self.loadCache() - - def test_pull(self): - before = self.loadCache() - actual = self.pclient.images.pull('prom/busybox:latest') - after = self.loadCache() - - self.assertEqual(len(before) + 1, len(after)) - self.assertIsNotNone( - next(iter([i for i in after if actual in i['id']] or []), None)) - - def test_search(self): - actual = self.pclient.images.search('alpine', 25) - names, length = itertools.tee(actual) - - for img in names: - self.assertIn('alpine', img.name) - self.assertTrue(0 < len(list(length)) <= 25) - - -if __name__ == '__main__': - unittest.main() diff --git a/contrib/python/podman/test/test_libs.py b/contrib/python/podman/test/test_libs.py deleted file mode 100644 index 202bed1d8..000000000 --- a/contrib/python/podman/test/test_libs.py +++ /dev/null @@ -1,53 +0,0 @@ -import datetime -import unittest - -import podman - - -class TestLibs(unittest.TestCase): - def setUp(self): - pass - - def tearDown(self): - pass - - def test_parse(self): - expected = datetime.datetime.strptime( - '2018-05-08T14:12:53.797795-0700', '%Y-%m-%dT%H:%M:%S.%f%z') - for v in [ - '2018-05-08T14:12:53.797795191-07:00', - '2018-05-08T14:12:53.797795-07:00', - '2018-05-08T14:12:53.797795-0700', - '2018-05-08 14:12:53.797795191 -0700 MST', - ]: - actual = podman.datetime_parse(v) - self.assertEqual(actual, expected) - - expected = datetime.datetime.strptime( - '2018-05-08T14:12:53.797795-0000', '%Y-%m-%dT%H:%M:%S.%f%z') - for v in [ - '2018-05-08T14:12:53.797795191Z', - '2018-05-08T14:12:53.797795191z', - ]: - actual = podman.datetime_parse(v) - self.assertEqual(actual, expected) - - actual = podman.datetime_parse(datetime.datetime.now().isoformat()) - self.assertIsNotNone(actual) - - def test_parse_fail(self): - for v in [ - 'There is no time here.', - ]: - with self.assertRaises(ValueError): - podman.datetime_parse(v) - - def test_format(self): - expected = '2018-05-08T18:24:52.753227-07:00' - dt = podman.datetime_parse(expected) - actual = podman.datetime_format(dt) - self.assertEqual(actual, expected) - - -if __name__ == '__main__': - unittest.main() diff --git a/contrib/python/podman/test/test_pods_ctnrs.py b/contrib/python/podman/test/test_pods_ctnrs.py deleted file mode 100644 index 009e30720..000000000 --- a/contrib/python/podman/test/test_pods_ctnrs.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -from test.podman_testcase import PodmanTestCase - -import podman -from podman import FoldedString - -pod = None - - -class TestPodsCtnrs(PodmanTestCase): - @classmethod - def setUpClass(cls): - # Populate storage - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def setUp(self): - self.tmpdir = os.environ['TMPDIR'] - self.host = os.environ['PODMAN_HOST'] - - self.pclient = podman.Client(self.host) - - def test_010_populate(self): - global pod - - pod = self.pclient.pods.create('pod1') - self.assertEqual('pod1', pod.name) - - img = self.pclient.images.get('docker.io/library/alpine:latest') - ctnr = img.container(pod=pod.id) - - pod.refresh() - self.assertEqual('1', pod.numberofcontainers) - self.assertEqual(ctnr.id, pod.containersinfo[0]['id']) - - def test_015_one_shot(self): - global pod - - details = pod.inspect() - state = FoldedString(details.containers[0]['state']) - self.assertEqual(state, 'configured') - - pod = pod.start() - status = FoldedString(pod.containersinfo[0]['status']) - # Race on whether container is still running or finished - self.assertIn(status, ('stopped', 'exited', 'running')) - - pod = pod.restart() - status = FoldedString(pod.containersinfo[0]['status']) - self.assertIn(status, ('stopped', 'exited', 'running')) - - # Pod kill is broken, so use stop for now - killed = pod.stop() - self.assertEqual(pod, killed) - - def test_999_remove(self): - global pod - - ident = pod.remove(force=True) - self.assertEqual(ident, pod.id) - - with self.assertRaises(StopIteration): - next(self.pclient.pods.list()) diff --git a/contrib/python/podman/test/test_pods_no_ctnrs.py b/contrib/python/podman/test/test_pods_no_ctnrs.py deleted file mode 100644 index 48b4f74e4..000000000 --- a/contrib/python/podman/test/test_pods_no_ctnrs.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -import unittest - -import podman -import varlink - -ident = None -pod = None - - -class TestPodsNoCtnrs(unittest.TestCase): - def setUp(self): - self.tmpdir = os.environ['TMPDIR'] - self.host = os.environ['PODMAN_HOST'] - - self.pclient = podman.Client(self.host) - - def test_010_create(self): - global ident - - actual = self.pclient.pods.create('pod0') - self.assertIsNotNone(actual) - ident = actual.id - - def test_015_list(self): - global ident, pod - - actual = next(self.pclient.pods.list()) - self.assertEqual('pod0', actual.name) - self.assertEqual(ident, actual.id) - self.assertEqual('Created', actual.status) - self.assertEqual('0', actual.numberofcontainers) - self.assertFalse(actual.containersinfo) - pod = actual - - def test_020_get(self): - global ident, pod - - actual = self.pclient.pods.get(pod.id) - self.assertEqual('pod0', actual.name) - self.assertEqual(ident, actual.id) - self.assertEqual('Created', actual.status) - self.assertEqual('0', actual.numberofcontainers) - self.assertFalse(actual.containersinfo) - - def test_025_inspect(self): - global ident, pod - - details = pod.inspect() - self.assertEqual(ident, details.id) - self.assertEqual('pod0', details.config['name']) - self.assertIsNone(details.containers) - - def test_030_ident_no_ctnrs(self): - global ident, pod - - actual = pod.kill() - self.assertEqual(pod, actual) - - actual = pod.pause() - self.assertEqual(pod, actual) - - actual = pod.unpause() - self.assertEqual(pod, actual) - - actual = pod.stop() - self.assertEqual(pod, actual) - - def test_045_raises_no_ctnrs(self): - global ident, pod - - with self.assertRaises(podman.NoContainersInPod): - pod.start() - - with self.assertRaises(podman.NoContainersInPod): - pod.restart() - - with self.assertRaises(podman.NoContainerRunning): - next(pod.stats()) - - with self.assertRaises(varlink.error.MethodNotImplemented): - pod.top() - - with self.assertRaises(varlink.error.MethodNotImplemented): - pod.wait() - - def test_999_remove(self): - global ident, pod - - actual = pod.remove() - self.assertEqual(ident, actual) - - with self.assertRaises(StopIteration): - next(self.pclient.pods.list()) diff --git a/contrib/python/podman/test/test_runner.sh b/contrib/python/podman/test/test_runner.sh deleted file mode 100755 index 651b2e74f..000000000 --- a/contrib/python/podman/test/test_runner.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/bash - -# podman needs to play some games with resources -if [[ $(id -u) != 0 ]]; then - echo >&2 $0 must be run as root. - exit 2 -fi - -# setup path to find new binaries _NOT_ system binaries -if [[ ! -x ../../../bin/podman ]]; then - echo 1>&2 Cannot find podman binary from libpod root directory. Run \"make binaries\" - exit 1 -fi -export PATH=../../../bin:$PATH - -function usage { - echo 1>&2 $0 '[-v] [-h] [test.<TestCase>|test.<TestCase>.<step>]' -} - -while getopts "vh" arg; do - case $arg in - v ) VERBOSE='-v'; export LOG_LEVEL=debug ;; - h ) usage ; exit 0 ;; - \? ) usage ; exit 2 ;; - esac -done -shift $((OPTIND -1)) - -function cleanup { - set +xeuo pipefail - # aggressive cleanup as tests may crash leaving crap around - umount '^(shm|nsfs)' - umount '\/run\/netns' - if [[ $RETURNCODE -eq 0 ]]; then - rm -r "$1" - fi -} - -# Create temporary directory for storage -export TMPDIR=`mktemp -d /tmp/podman.XXXXXXXXXX` -trap "cleanup $TMPDIR" EXIT - -function umount { - set +xeuo pipefail - # xargs -r always ran once, so write any mount points to file first - mount |awk "/$1/"' { print $3 }' >${TMPDIR}/mounts - if [[ -s ${TMPDIR}/mounts ]]; then - xargs <${TMPDIR}/mounts -t umount - fi -} - -function showlog { - [[ -s $1 ]] && cat <<-EOT -$1 ===== -$(cat "$1") - -EOT -} - -# Need locations to store stuff -mkdir -p ${TMPDIR}/{podman,crio,crio-run,cni/net.d,ctnr,tunnel} - -# Cannot be done in python unittest fixtures. EnvVar not picked up. -export REGISTRIES_CONFIG_PATH=${TMPDIR}/registry.conf -cat >$REGISTRIES_CONFIG_PATH <<-EOT - [registries.search] - registries = ['docker.io'] - [registries.insecure] - registries = [] - [registries.block] - registries = [] -EOT - -export CNI_CONFIG_PATH=${TMPDIR}/cni/net.d -cat >$CNI_CONFIG_PATH/87-podman-bridge.conflist <<-EOT -{ - "cniVersion": "0.3.0", - "name": "podman", - "plugins": [{ - "type": "bridge", - "bridge": "cni0", - "isGateway": true, - "ipMasq": true, - "ipam": { - "type": "host-local", - "subnet": "10.88.0.0/16", - "routes": [{ - "dst": "0.0.0.0/0" - }] - } - }, - { - "type": "portmap", - "capabilities": { - "portMappings": true - } - } - ] -} -EOT - -cat >$TMPDIR/ctnr/hello.sh <<-EOT -echo 'Hello, World' -exit 0 -EOT - -cat >$TMPDIR/ctnr/Dockerfile <<-EOT -FROM alpine:latest -COPY ./hello.sh /tmp/ -RUN chmod 755 /tmp/hello.sh -ENTRYPOINT ["/tmp/hello.sh"] -EOT - -export PODMAN_HOST="unix:${TMPDIR}/podman/io.podman" -PODMAN_ARGS="--storage-driver=vfs \ - --root=${TMPDIR}/crio \ - --runroot=${TMPDIR}/crio-run \ - --cni-config-dir=$CNI_CONFIG_PATH \ - --cgroup-manager=cgroupfs \ - " -if [[ -n $VERBOSE ]]; then - PODMAN_ARGS="$PODMAN_ARGS --log-level=$LOG_LEVEL" -fi -PODMAN="podman $PODMAN_ARGS" - -cat <<-EOT |tee /tmp/test_podman.output -$($PODMAN --version) -$PODMAN varlink --timeout=0 ${PODMAN_HOST} -========================================== -EOT - -# Run podman in background without systemd for test purposes -$PODMAN varlink --timeout=0 ${PODMAN_HOST} >>/tmp/test_podman.output 2>&1 & -if [[ $? != 0 ]]; then - echo 1>&2 Failed to start podman - showlog /tmp/test_podman.output -fi - -if [[ -z $1 ]]; then - export PYTHONPATH=. - python3 -m unittest discover -s . $VERBOSE - RETURNCODE=$? -else - export PYTHONPATH=.:./test - python3 -m unittest $1 $VERBOSE - RETURNCODE=$? -fi - -pkill -9 podman -pkill -9 conmon - -showlog /tmp/test_podman.output -showlog /tmp/alpine.log -showlog /tmp/busybox.log - -exit $RETURNCODE diff --git a/contrib/python/podman/test/test_system.py b/contrib/python/podman/test/test_system.py deleted file mode 100644 index c483f3232..000000000 --- a/contrib/python/podman/test/test_system.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import unittest -from urllib.parse import urlparse - -import podman -import varlink - - -class TestSystem(unittest.TestCase): - def setUp(self): - self.host = os.environ['PODMAN_HOST'] - self.tmpdir = os.environ['TMPDIR'] - - def tearDown(self): - pass - - def test_bad_address(self): - with self.assertRaisesRegex(varlink.client.ConnectionError, - "Invalid address 'bad address'"): - podman.Client('bad address') - - def test_ping(self): - with podman.Client(self.host) as pclient: - self.assertTrue(pclient.system.ping()) - - @unittest.skip("TODO: Need to setup ssh under Travis") - def test_remote_ping(self): - host = urlparse(self.host) - remote_uri = 'ssh://root@localhost{}'.format(host.path) - - local_uri = 'unix:{}/tunnel/podman.sock'.format(self.tmpdir) - with podman.Client( - uri=local_uri, - remote_uri=remote_uri, - identity_file=os.path.expanduser('~/.ssh/id_rsa'), - ) as remote_client: - self.assertTrue(remote_client.system.ping()) - - def test_versions(self): - with podman.Client(self.host) as pclient: - # Values change with each build so we cannot test too much - self.assertListEqual( - sorted([ - 'built', 'client_version', 'git_commit', 'go_version', - 'os_arch', 'version' - ]), sorted(list(pclient.system.versions._fields))) - pclient.system.versions - self.assertIsNot(podman.__version__, '0.0.0') - - def test_info(self): - with podman.Client(self.host) as pclient: - actual = pclient.system.info() - # Values change too much to do exhaustive testing - self.assertIsNotNone(actual.podman['go_version']) - self.assertListEqual( - sorted([ - 'host', 'insecure_registries', 'podman', 'registries', - 'store' - ]), sorted(list(actual._fields))) - - -if __name__ == '__main__': - unittest.main() diff --git a/contrib/python/podman/test/test_tunnel.py b/contrib/python/podman/test/test_tunnel.py deleted file mode 100644 index 9a33e76cd..000000000 --- a/contrib/python/podman/test/test_tunnel.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import absolute_import - -import logging -import time -import unittest -from unittest.mock import MagicMock, patch - -from podman.libs.tunnel import Context, Portal, Tunnel - - -class TestTunnel(unittest.TestCase): - def setUp(self): - self.tunnel_01 = MagicMock(spec=Tunnel) - self.tunnel_02 = MagicMock(spec=Tunnel) - - def test_portal_ops(self): - portal = Portal(sweap=500) - portal['unix:/01'] = self.tunnel_01 - portal['unix:/02'] = self.tunnel_02 - - self.assertEqual(portal.get('unix:/01'), self.tunnel_01) - self.assertEqual(portal.get('unix:/02'), self.tunnel_02) - - del portal['unix:/02'] - with self.assertRaises(KeyError): - portal['unix:/02'] - self.assertEqual(len(portal), 1) - - def test_portal_reaping(self): - portal = Portal(sweap=0.5) - portal['unix:/01'] = self.tunnel_01 - portal['unix:/02'] = self.tunnel_02 - - self.assertEqual(len(portal), 2) - for entry in portal: - self.assertIn(entry, (self.tunnel_01, self.tunnel_02)) - - time.sleep(1) - portal.reap() - self.assertEqual(len(portal), 0) - - def test_portal_no_reaping(self): - portal = Portal(sweap=500) - portal['unix:/01'] = self.tunnel_01 - portal['unix:/02'] = self.tunnel_02 - - portal.reap() - self.assertEqual(len(portal), 2) - for entry in portal: - self.assertIn(entry, (self.tunnel_01, self.tunnel_02)) - - @patch('subprocess.Popen') - @patch('os.path.exists', return_value=True) - @patch('weakref.finalize') - def test_tunnel(self, mock_finalize, mock_exists, mock_Popen): - mock_Popen.return_value.returncode = 0 - - context = Context( - 'unix:/01', - 'io.podman', - '/tmp/user/socket', - '/run/podman/socket', - 'user', - 'hostname', - None, - '~/.ssh/id_rsa', - ) - tunnel = Tunnel(context).bore() - - cmd = ['ssh', '-fNT'] - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - cmd.append('-v') - else: - cmd.append('-q') - - cmd.extend(( - '-L', - '{}:{}'.format(context.local_socket, context.remote_socket), - '-i', - context.identity_file, - '{}@{}'.format(context.username, context.hostname), - )) - - mock_finalize.assert_called_once_with(tunnel, tunnel.close) - mock_exists.assert_called_once_with(context.local_socket) - mock_Popen.assert_called_once_with(cmd, close_fds=True) diff --git a/contrib/python/podman/tox.ini b/contrib/python/podman/tox.ini deleted file mode 100644 index 797eafbe3..000000000 --- a/contrib/python/podman/tox.ini +++ /dev/null @@ -1,8 +0,0 @@ -[tox] -envlist = py34,py35,py36 -skipdist = True - -[testenv] -deps=-rrequirements.txt -whitelist_externals = bash -commands=bash test/test_runner.sh diff --git a/contrib/python/pypodman/.pylintrc b/contrib/python/pypodman/.pylintrc deleted file mode 100644 index a5628a6cf..000000000 --- a/contrib/python/pypodman/.pylintrc +++ /dev/null @@ -1,564 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=0 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - locally-enabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package.. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )?<?https?://\S+>?$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[BASIC] - -# Naming style matching correct argument names. -#argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -argument-rgx=[a-z_][a-z0-9_]{1,30}$ -argument-name-hint=[a-z_][a-z0-9_]{1,30}$ - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=c, - e, - i, - j, - k, - r, - v, - ex, - Run, - _ - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -#variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -variable-rgx=[a-z_][a-z0-9_]{2,30}$ -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[DESIGN] - -# Support argparse.Action constructor API -# Maximum number of arguments for function / method. -max-args=12 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement. -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=10 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception". -overgeneral-exceptions=Exception diff --git a/contrib/python/pypodman/MANIFEST.in b/contrib/python/pypodman/MANIFEST.in deleted file mode 100644 index 72e638cb9..000000000 --- a/contrib/python/pypodman/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -prune test/ -include README.md diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile deleted file mode 100644 index 230eee44d..000000000 --- a/contrib/python/pypodman/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -PYTHON ?= $(shell command -v python3 2>/dev/null || command -v python) -DESTDIR := / -PODMAN_VERSION ?= '0.11.1.1' - -.PHONY: python-pypodman -python-pypodman: - PODMAN_VERSION=$(PODMAN_VERSION) \ - $(PYTHON) setup.py sdist bdist - -.PHONY: lint -lint: - $(PYTHON) -m pylint pypodman - -.PHONY: integration -integration: - true - -.PHONY: install -install: - PODMAN_VERSION=$(PODMAN_VERSION) \ - $(PYTHON) setup.py install --root ${DESTDIR} - -.PHONY: upload -upload: - PODMAN_VERSION=$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel - twine upload --repository-url https://test.pypi.org/legacy/ dist/* - -.PHONY: clobber -clobber: uninstall clean - -.PHONY: uninstall - $(PYTHON) -m pip uninstall --yes pypodman ||: - -.PHONY: clean -clean: - rm -rf pypodman.egg-info dist - find . -depth -name __pycache__ -exec rm -rf {} \; - find . -depth -name \*.pyc -exec rm -f {} \; - $(PYTHON) ./setup.py clean --all diff --git a/contrib/python/pypodman/README.md b/contrib/python/pypodman/README.md deleted file mode 100644 index 6991daffa..000000000 --- a/contrib/python/pypodman/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# pypodman - CLI for podman written in python - -## Status: Active Development - -See [libpod](https://github.com/containers/libpod/contrib/python/pypodman) - -## Releases - -To build the pypodman egg and install as user: - -```sh -cd ~/libpod/contrib/python/pypodman -python3 setup.py clean -a && python3 setup.py sdist bdist -python3 setup.py install --user -``` -Add `~/.local/bin` to your `PATH` to run pypodman command. - -## Running command: - -### Against local podman service -```sh -$ pypodman images -``` -### Against remote podman service -```sh -$ pypodman --host node001.example.org images -``` -### Full help system available -```sh -$ pypodman -h -``` -```sh -$ pypodman images -h -``` diff --git a/contrib/python/pypodman/docs/man1/pypodman.1 b/contrib/python/pypodman/docs/man1/pypodman.1 deleted file mode 100644 index 45472dab0..000000000 --- a/contrib/python/pypodman/docs/man1/pypodman.1 +++ /dev/null @@ -1,101 +0,0 @@ -.TH pypodman 1 2018-07-20 0.7.3 -.SH NAME -pypodman \- CLI management tool for containers and images -.SH SYNOPSIS -\f[B]pypodman\f[] [\f[I]global options\f[]] \f[I]command\f[] [\f[I]options\f[]] -.SH DESCRIPTION -pypodman is a simple client only tool to help with debugging issues when daemons -such as CRI runtime and the kubelet are not responding or failing. -.P -pypodman uses a VarLink API to commicate with a podman service running on either -the local or remote machine. pypodman uses ssh to create secure tunnels when -communicating with a remote service. -.SH GLOBAL OPTIONS -.PP -\f[B]\[en]help, \-h\f[] -.PP -Print usage statement. -.PP -\f[B]\[en]version\f[] -.PP -Print program version number and exit. -.PP -\f[B]\[en]config\-home\f[] -.PP -Directory that will be namespaced with \f[C]pypodman\f[] to hold -\f[C]pypodman.conf\f[]. -See FILES below for more details. -.PP -\f[B]\[en]log\-level\f[] -.PP -Log events above specified level: DEBUG, INFO, WARNING (default), ERROR, -or CRITICAL. -.PP -\f[B]\[en]run\-dir\f[] -.PP -Directory that will be namespaced with \f[C]pypodman\f[] to hold local socket -bindings. The default is `\f[C]$XDG_RUNTIME_DIR\\\f[]. -.PP -\f[B]\[en]user\f[] -.PP -Authenicating user on remote host. \f[C]pypodman\f[] defaults to the logged in -user. -.PP -\f[B]\[en]host\f[] -.PP -Name of remote host. There is no default, if not given \f[C]pypodman\f[] -attempts to connect to \f[C]\-\-remote\-socket\-path\f[] on local host. -.PP -\f[B]\[en]port\f[] -.PP -The optional port for \f[C]ssh\f[] to connect tunnel to on remote host. -Default is None and will allow \f[C]ssh\f[] to follow it's default configuration. -.PP -\f[B]\[en]remote\-socket\-path\f[] -.PP -Path on remote host for podman service's \f[C]AF_UNIX\f[] socket. The default is -\f[C]/run/podman/io.podman\f[]. -.PP -\f[B]\[en]identity\-file\f[] -.PP -The optional \f[C]ssh\f[] identity file to authenicate when tunnelling to remote -host. Default is None and will allow \f[C]ssh\f[] to follow it's default methods -for resolving the identity and private key using the logged in user. -.SH COMMANDS -.PP -See podman(1) (podman.1.md) -.SH FILES -.PP -\f[B]pypodman/pypodman.conf\f[] -(\f[C]Any\ element\ of\ XDG_CONFIG_DIRS\f[] and/or -\f[C]XDG_CONFIG_HOME\f[] and/or \f[B]\[en]config\-home\f[]) -.PP -pypodman.conf is one or more configuration files for running the pypodman -command. pypodman.conf is a TOML file with the stanza \f[C][default]\f[], with a -map of \f[C]option: value\f[]. -.PP -pypodman follows the XDG (freedesktop.org) conventions for resolving it's -configuration. The list below are read from top to bottom with later items -overwriting earlier. Any missing items are ignored. -.IP \[bu] 2 -\f[C]pypodman/pypodman.conf\f[] from any path element in -\f[C]XDG_CONFIG_DIRS\f[] or \f[C]\\etc\\xdg\f[] -.IP \[bu] 2 -\f[C]XDG_CONFIG_HOME\f[] or $HOME/.config + \f[C]pypodman/pypodman.conf\f[] -.IP \[bu] 2 -From \f[C]\-\-config\-home\f[] command line option + \f[C]pypodman/pypodman.conf\f[] -.IP \[bu] 2 -From environment variable prefixed with PODMAN_, for example: PODMAN_RUN_DIR -.IP \[bu] 2 -From command line option, for example: \[en]run\-dir -.PP -This should provide Operators the ability to setup basic configurations -and allow users to customize them. -.PP -\f[B]XDG_RUNTIME_DIR\f[] (\f[C]XDG_RUNTIME_DIR/io.podman\f[]) -.PP -Directory where pypodman stores non\-essential runtime files and other file -objects (such as sockets, named pipes, \&...). -.SH SEE ALSO -.PP -\f[C]podman(1)\f[], \f[C]libpod(8)\f[] diff --git a/contrib/python/pypodman/pypodman/__init__.py b/contrib/python/pypodman/pypodman/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/contrib/python/pypodman/pypodman/__init__.py +++ /dev/null diff --git a/contrib/python/pypodman/pypodman/lib/__init__.py b/contrib/python/pypodman/pypodman/lib/__init__.py deleted file mode 100644 index d9a434254..000000000 --- a/contrib/python/pypodman/pypodman/lib/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Remote podman client support library.""" -import sys - -import podman -from pypodman.lib.action_base import AbstractActionBase -from pypodman.lib.parser_actions import (ChangeAction, PathAction, - PositiveIntAction, SignalAction, - UnitAction) -from pypodman.lib.podman_parser import PodmanArgumentParser -from pypodman.lib.report import Report, ReportColumn - -# Silence pylint overlording... -assert ChangeAction -assert PathAction -assert PositiveIntAction -assert SignalAction -assert UnitAction - -__all__ = [ - 'AbstractActionBase', - 'PodmanArgumentParser', - 'Report', - 'ReportColumn', -] - - -def query_model(model, identifiers=None): - """Retrieve all (default) or given model(s).""" - objs = [] - if identifiers is None: - objs.extend(model.list()) - else: - try: - for ident in identifiers: - objs.append(model.get(ident)) - except ( - podman.PodNotFound, - podman.ImageNotFound, - podman.ContainerNotFound, - ) as ex: - print( - '"{}" not found'.format(ex.name), file=sys.stderr, flush=True) - return objs diff --git a/contrib/python/pypodman/pypodman/lib/action_base.py b/contrib/python/pypodman/pypodman/lib/action_base.py deleted file mode 100644 index 5cba7ac5c..000000000 --- a/contrib/python/pypodman/pypodman/lib/action_base.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Base class for all actions of remote client.""" -import abc -from functools import lru_cache - -import podman - - -class AbstractActionBase(abc.ABC): - """Base class for all actions of remote client.""" - - @classmethod - @abc.abstractmethod - def subparser(cls, parent): - """Define parser for this action. Subclasses must implement. - - API: - Use set_defaults() to set attributes "class_" and "method". These will - be invoked as class_(parsed_args).method() - """ - parent.add_flag( - '--all', - help='list all items.') - parent.add_flag( - '--truncate', - '--trunc', - default=True, - help="Truncate id's and other long fields.") - parent.add_flag( - '--heading', - default=True, - help='Include table headings in the output.') - parent.add_flag( - '--quiet', - help='List only the IDs.') - - def __init__(self, args): - """Construct class.""" - # Dump all unset arguments before transmitting to service - self._args = args - self.opts = { - k: v - for k, v in vars(self._args).items() if v is not None - } - - @property - def remote_uri(self): - """URI for remote side of connection.""" - return self._args.remote_uri - - @property - def local_uri(self): - """URI for local side of connection.""" - return self._args.local_uri - - @property - def identity_file(self): - """Key for authenication.""" - return self._args.identity_file - - @property - @lru_cache(maxsize=1) - def client(self): - """Podman remote client for communicating.""" - if self._args.host is None: - return podman.Client(uri=self.local_uri) - return podman.Client( - uri=self.local_uri, - remote_uri=self.remote_uri, - identity_file=self.identity_file) - - def __repr__(self): - """Compute the “official” string representation of object.""" - return ("{}(local_uri='{}', remote_uri='{}'," - " identity_file='{}')").format( - self.__class__, - self.local_uri, - self.remote_uri, - self.identity_file, - ) diff --git a/contrib/python/pypodman/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/pypodman/lib/actions/__init__.py deleted file mode 100644 index c0d77ddb1..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Module to export all the podman subcommands.""" -from pypodman.lib.actions.attach_action import Attach -from pypodman.lib.actions.commit_action import Commit -from pypodman.lib.actions.create_action import Create -from pypodman.lib.actions.export_action import Export -from pypodman.lib.actions.history_action import History -from pypodman.lib.actions.images_action import Images -from pypodman.lib.actions.import_action import Import -from pypodman.lib.actions.info_action import Info -from pypodman.lib.actions.inspect_action import Inspect -from pypodman.lib.actions.kill_action import Kill -from pypodman.lib.actions.logs_action import Logs -from pypodman.lib.actions.mount_action import Mount -from pypodman.lib.actions.pause_action import Pause -from pypodman.lib.actions.pod_action import Pod -from pypodman.lib.actions.port_action import Port -from pypodman.lib.actions.ps_action import Ps -from pypodman.lib.actions.pull_action import Pull -from pypodman.lib.actions.push_action import Push -from pypodman.lib.actions.restart_action import Restart -from pypodman.lib.actions.rm_action import Rm -from pypodman.lib.actions.rmi_action import Rmi -from pypodman.lib.actions.run_action import Run -from pypodman.lib.actions.search_action import Search -from pypodman.lib.actions.start_action import Start -from pypodman.lib.actions.version_action import Version - -__all__ = [ - 'Attach', - 'Commit', - 'Create', - 'Export', - 'History', - 'Images', - 'Import', - 'Info', - 'Inspect', - 'Kill', - 'Logs', - 'Mount', - 'Pause', - 'Pod', - 'Port', - 'Ps', - 'Pull', - 'Push', - 'Restart', - 'Rm', - 'Rmi', - 'Run', - 'Search', - 'Start', - 'Version', -] diff --git a/contrib/python/pypodman/pypodman/lib/actions/_create_args.py b/contrib/python/pypodman/pypodman/lib/actions/_create_args.py deleted file mode 100644 index 8ab4292e8..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/_create_args.py +++ /dev/null @@ -1,401 +0,0 @@ -"""Implement common create container arguments together.""" - -from pypodman.lib import SignalAction, UnitAction - - -class CreateArguments(): - """Helper to add all the create flags to a command.""" - - @classmethod - def add_arguments(cls, parser): - """Add CreateArguments to parser.""" - parser.add_argument( - '--add-host', - action='append', - metavar='HOST', - help='Add a line to /etc/hosts.' - ' The option can be set multiple times.' - ' (format: hostname:ip)') - parser.add_argument( - '--annotation', - action='append', - help='Add an annotation to the container.' - 'The option can be set multiple times.' - '(format: key=value)') - parser.add_argument( - '--attach', - '-a', - action='append', - metavar='FD', - help=('Attach to STDIN, STDOUT or STDERR. The option can be set' - ' for each of stdin, stdout, and stderr.')) - parser.add_argument( - '--blkio-weight', - choices=range(10, 1000), - metavar='[10-1000]', - help=('Block IO weight (relative weight) accepts a' - ' weight value between 10 and 1000.')) - parser.add_argument( - '--blkio-weight-device', - action='append', - metavar='WEIGHT', - help='Block IO weight, relative device weight.' - ' (format: DEVICE_NAME:WEIGHT)') - parser.add_argument( - '--cap-add', - action='append', - metavar='CAP', - help=('Add Linux capabilities' - 'The option can be set multiple times.')) - parser.add_argument( - '--cap-drop', - action='append', - metavar='CAP', - help=('Drop Linux capabilities' - 'The option can be set multiple times.')) - parser.add_argument( - '--cgroup-parent', - metavar='PATH', - help='Path to cgroups under which the cgroup for the' - ' container will be created. If the path is not' - ' absolute, the path is considered to be relative' - ' to the cgroups path of the init process. Cgroups' - ' will be created if they do not already exist.') - parser.add_argument( - '--cidfile', - metavar='PATH', - help='Write the container ID to the file, on the remote host.') - parser.add_argument( - '--conmon-pidfile', - metavar='PATH', - help=('Write the pid of the conmon process to a file,' - ' on the remote host.')) - parser.add_argument( - '--cpu-period', - type=int, - metavar='PERIOD', - help=('Limit the CPU CFS (Completely Fair Scheduler) period.')) - parser.add_argument( - '--cpu-quota', - type=int, - metavar='QUOTA', - help=('Limit the CPU CFS (Completely Fair Scheduler) quota.')) - parser.add_argument( - '--cpu-rt-period', - type=int, - metavar='PERIOD', - help=('Limit the CPU real-time period in microseconds.')) - parser.add_argument( - '--cpu-rt-runtime', - type=int, - metavar='LIMIT', - help=('Limit the CPU real-time runtime in microseconds.')) - parser.add_argument( - '--cpu-shares', - type=int, - metavar='SHARES', - help=('CPU shares (relative weight)')) - parser.add_argument( - '--cpus', - type=float, - help=('Number of CPUs. The default is 0.0 which means no limit')) - parser.add_argument( - '--cpuset-cpus', - metavar='LIST', - help=('CPUs in which to allow execution (0-3, 0,1)')) - parser.add_argument( - '--cpuset-mems', - metavar='NODES', - help=('Memory nodes (MEMs) in which to allow execution (0-3, 0,1).' - ' Only effective on NUMA systems')) - parser.add_flag( - '--detach', - '-d', - help='Detached mode: run the container in the background and' - ' print the new container ID. (default: False)') - parser.add_argument( - '--detach-keys', - metavar='KEY(s)', - default=4, - help='Override the key sequence for detaching a container.' - ' (format: a single character [a-Z] or ctrl-<value> where' - ' <value> is one of: a-z, @, ^, [, , or _)') - parser.add_argument( - '--device', - action='append', - help=('Add a host device to the container' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--device-read-bps', - action='append', - metavar='LIMIT', - help=('Limit read rate (bytes per second) from a device' - ' (e.g. --device-read-bps=/dev/sda:1mb)' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--device-read-iops', - action='append', - metavar='LIMIT', - help=('Limit read rate (IO per second) from a device' - ' (e.g. --device-read-iops=/dev/sda:1000)' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--device-write-bps', - action='append', - metavar='LIMIT', - help=('Limit write rate (bytes per second) to a device' - ' (e.g. --device-write-bps=/dev/sda:1mb)' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--device-write-iops', - action='append', - metavar='LIMIT', - help=('Limit write rate (IO per second) to a device' - ' (e.g. --device-write-iops=/dev/sda:1000)' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--dns', - action='append', - metavar='SERVER', - help=('Set custom DNS servers.' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--dns-option', - action='append', - metavar='OPT', - help=('Set custom DNS options.' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--dns-search', - action='append', - metavar='DOMAIN', - help=('Set custom DNS search domains.' - 'The option can be set multiple times.'), - ) - parser.add_argument( - '--entrypoint', - help=('Overwrite the default ENTRYPOINT of the image.'), - ) - parser.add_argument( - '--env', - '-e', - action='append', - help=('Set environment variables.'), - ) - parser.add_argument( - '--env-file', - help=('Read in a line delimited file of environment variables,' - ' on the remote host.'), - ) - parser.add_argument( - '--expose', - action='append', - metavar='RANGE', - help=('Expose a port, or a range of ports' - ' (e.g. --expose=3300-3310) to set up port redirection.'), - ) - parser.add_argument( - '--gidmap', - metavar='MAP', - action='append', - help=('GID map for the user namespace'), - ) - parser.add_argument( - '--group-add', - action='append', - metavar='GROUP', - help=('Add additional groups to run as')) - parser.add_argument('--hostname', help='Container host name') - - # only way for argparse to handle these options. - vol_args = { - 'choices': ('bind', 'tmpfs', 'ignore'), - 'metavar': 'MODE', - 'type': str.lower, - 'help': 'Tells podman how to handle the builtin image volumes', - } - - volume_group = parser.add_mutually_exclusive_group() - volume_group.add_argument('--image-volume', **vol_args) - volume_group.add_argument('--builtin-volume', **vol_args) - - parser.add_flag( - '--interactive', - '-i', - help='Keep STDIN open even if not attached.') - parser.add_argument('--ipc', help='Create namespace') - parser.add_argument( - '--kernel-memory', action=UnitAction, help='Kernel memory limit') - parser.add_argument( - '--label', - '-l', - action='append', - help=('Add metadata to a container' - ' (e.g., --label com.example.key=value)')) - parser.add_argument( - '--label-file', help='Read in a line delimited file of labels') - parser.add_argument( - '--log-driver', - choices='json-file', - metavar='json-file', - default='json-file', - help='Logging driver for the container. (default: %(default)s)') - parser.add_argument( - '--log-opt', - action='append', - help='Logging driver specific options') - parser.add_argument( - '--memory', '-m', action=UnitAction, help='Memory limit') - parser.add_argument( - '--memory-reservation', - action=UnitAction, - help='Memory soft limit') - parser.add_argument( - '--memory-swap', - action=UnitAction, - help=('A limit value equal to memory plus swap.' - 'Must be used with the --memory flag')) - parser.add_argument( - '--memory-swappiness', - choices=range(0, 100), - metavar='[0-100]', - help="Tune a container's memory swappiness behavior") - parser.add_argument('--name', help='Assign a name to the container') - parser.add_argument( - '--network', - '--net', - metavar='BRIDGE', - help='Set the Network mode for the container.' - ' (format: bridge, host, container:UUID, ns:PATH, none)') - parser.add_flag( - '--oom-kill-disable', - help='Whether to disable OOM Killer for the container or not.') - parser.add_argument( - '--oom-score-adj', - choices=range(-1000, 1000), - metavar='[-1000-1000]', - help="Tune the host's OOM preferences for containers") - parser.add_argument( - '--pid', - help='Set the PID Namespace mode for the container.' - '(format: host, container:UUID, ns:PATH)') - parser.add_argument( - '--pids-limit', - type=int, - metavar='LIMIT', - help=("Tune the container's pids limit." - " Set -1 to have unlimited pids for the container.")) - parser.add_argument('--pod', help='Run container in an existing pod') - parser.add_flag( - '--privileged', - help='Give extended privileges to this container.') - parser.add_argument( - '--publish', - '-p', - metavar='RANGE', - help="Publish a container's port, or range of ports, to the host") - parser.add_flag( - '--publish-all', - '-P', - help='Publish all exposed ports to random' - ' ports on the host interfaces.') - parser.add_flag( - '--quiet', - '-q', - help='Suppress output information when pulling images') - parser.add_flag( - '--read-only', - help="Mount the container's root filesystem as read only.") - parser.add_flag( - '--rm', - help='Automatically remove the container when it exits.') - parser.add_argument( - '--rootfs', - help='If specified, the first argument refers to an' - ' exploded container on the file system of remote host.') - parser.add_argument( - '--security-opt', - action='append', - metavar='OPT', - help='Set security options.') - parser.add_argument( - '--shm-size', action=UnitAction, help='Size of /dev/shm') - parser.add_flag( - '--sig-proxy', - help='Proxy signals sent to the podman run' - ' command to the container process') - parser.add_argument( - '--stop-signal', - action=SignalAction, - default='TERM', - help='Signal to stop a container') - parser.add_argument( - '--stop-timeout', - metavar='TIMEOUT', - type=int, - default=10, - help='Seconds to wait on stopping container.') - parser.add_argument( - '--subgidname', - metavar='MAP', - help='Name for GID map from the /etc/subgid file') - parser.add_argument( - '--subuidname', - metavar='MAP', - help='Name for UID map from the /etc/subuid file') - parser.add_argument( - '--sysctl', - action='append', - help='Configure namespaced kernel parameters at runtime') - parser.add_argument( - '--tmpfs', - action='append', - metavar='MOUNT', - help='Create a tmpfs mount.' - ' (default: rw,noexec,nosuid,nodev,size=65536k.)') - parser.add_flag( - '--tty', - '-t', - help='Allocate a pseudo-TTY for standard input of container.') - parser.add_argument( - '--uidmap', - action='append', - metavar='MAP', - help='UID map for the user namespace') - parser.add_argument( - '--ulimit', - action='append', - metavar='OPT', - help='Ulimit options', - ) - parser.add_argument( - '--user', - '-u', - help='Sets the username or UID used and optionally' - ' the groupname or GID for the specified command.') - parser.add_argument( - '--userns', - metavar='NAMESPACE', - help='Set the user namespace mode for the container') - parser.add_argument( - '--uts', - choices=('host', 'ns'), - type=str.lower, - help='Set the UTS mode for the container') - parser.add_argument('--volume', '-v', help='Create a bind mount.') - parser.add_argument( - '--volumes-from', - action='append', - help='Mount volumes from the specified container(s).') - parser.add_argument( - '--workdir', - '-w', - metavar='PATH', - help='Working directory inside the container') diff --git a/contrib/python/pypodman/pypodman/lib/actions/attach_action.py b/contrib/python/pypodman/pypodman/lib/actions/attach_action.py deleted file mode 100644 index e9829e894..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/attach_action.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Remote client command for attaching to a container.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Attach(AbstractActionBase): - """Class for attaching to a running container.""" - - @classmethod - def subparser(cls, parent): - """Add Attach command to parent parser.""" - parser = parent.add_parser('attach', help='attach to container') - parser.add_argument( - '--image', - help='image to instantiate and attach to', - ) - parser.add_argument( - 'command', - nargs='*', - help='image to instantiate and attach to', - ) - parser.set_defaults(class_=cls, method='attach') - - def __init__(self, args): - """Construct Attach class.""" - super().__init__(args) - if not args.image: - raise ValueError('You must supply one image id' - ' or name to be attached.') - - def attach(self): - """Attach to instantiated image.""" - args = { - 'detach': True, - 'tty': True, - } - if self._args.command: - args['command'] = self._args.command - - try: - try: - ident = self.client.images.pull(self._args.image) - img = self.client.images.get(ident) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - return 1 - - ctnr = img.create(**args) - ctnr.attach(eot=4) - - try: - ctnr.start() - print() - except (BrokenPipeError, KeyboardInterrupt): - print('\nContainer disconnected.') - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py deleted file mode 100644 index c166e1aff..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Remote client command for creating image from container.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase, ChangeAction - - -class Commit(AbstractActionBase): - """Class for creating image from container.""" - - @classmethod - def subparser(cls, parent): - """Add Commit command to parent parser.""" - parser = parent.add_parser( - 'commit', - help='create image from container', - ) - parser.add_argument( - '--author', - help='Set the author for the committed image', - ) - parser.add_argument( - '--change', - '-c', - action=ChangeAction, - ) - parser.add_argument( - '--format', - '-f', - choices=('oci', 'docker'), - default='oci', - type=str.lower, - help='Set the format of the image manifest and metadata.' - ' (Ignored.)', - ) - parser.add_argument( - '--iidfile', - metavar='PATH', - help='Write the image ID to the file', - ) - parser.add_argument( - '--message', - '-m', - help='Set commit message for committed image' - ' (Only on docker images.)', - ) - parser.add_flag( - '--pause', - '-p', - help='Pause the container when creating an image', - ) - parser.add_flag( - '--quiet', - '-q', - help='Suppress output', - ) - parser.add_argument( - 'container', - nargs=1, - help='container to use as source', - ) - parser.add_argument( - 'image', - nargs=1, - help='image name to create', - ) - parser.set_defaults(class_=cls, method='commit') - - def commit(self): - """Create image from container.""" - try: - try: - ctnr = self.client.containers.get(self._args.container[0]) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - return 1 - else: - ident = ctnr.commit( - self.opts['image'][0], - change=self.opts.get('change', None), - message=self.opts.get('message', None), - pause=self.opts['pause'], - author=self.opts.get('author', None), - ) - - if not self.opts['quiet']: - print(ident) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/create_action.py b/contrib/python/pypodman/pypodman/lib/actions/create_action.py deleted file mode 100644 index 26a312bb1..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/create_action.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Remote client command for creating container from image.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - -from ._create_args import CreateArguments - - -class Create(AbstractActionBase): - """Class for creating container from image.""" - - @classmethod - def subparser(cls, parent): - """Add Create command to parent parser.""" - parser = parent.add_parser( - 'create', help='create container from image') - - CreateArguments.add_arguments(parser) - - parser.add_argument('image', nargs=1, help='source image id') - parser.add_argument( - 'command', - nargs=parent.REMAINDER, - help='command and args to run.', - ) - parser.set_defaults(class_=cls, method='create') - - def __init__(self, args): - """Construct Create class.""" - super().__init__(args) - - # image id used only on client - del self.opts['image'] - - def create(self): - """Create container.""" - try: - for ident in self._args.image: - try: - img = self.client.images.get(ident) - img.container(**self.opts) - print(ident) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/export_action.py b/contrib/python/pypodman/pypodman/lib/actions/export_action.py deleted file mode 100644 index 7ef178c4c..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/export_action.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Remote client command for export container filesystem to tarball.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Export(AbstractActionBase): - """Class for exporting container filesystem to tarball.""" - - @classmethod - def subparser(cls, parent): - """Add Export command to parent parser.""" - parser = parent.add_parser( - 'export', - help='export container to tarball', - ) - parser.add_argument( - '--output', - '-o', - metavar='PATH', - nargs=1, - required=True, - help='Write to this file on host', - ) - parser.add_argument( - 'container', - nargs=1, - help='container to use as source', - ) - parser.set_defaults(class_=cls, method='export') - - def export(self): - """Create tarball from container filesystem.""" - try: - try: - ctnr = self.client.containers.get(self._args.container[0]) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - return 1 - else: - ctnr.export(self._args.output[0]) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/history_action.py b/contrib/python/pypodman/pypodman/lib/actions/history_action.py deleted file mode 100644 index 76c3ad756..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/history_action.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Remote client for reporting image history.""" -import json -from collections import OrderedDict - -import humanize - -import podman -from pypodman.lib import AbstractActionBase, Report, ReportColumn - - -class History(AbstractActionBase): - """Class for reporting Image History.""" - - @classmethod - def subparser(cls, parent): - """Add History command to parent parser.""" - parser = parent.add_parser('history', help='report image history') - super().subparser(parser) - parser.add_flag( - '--human', - '-H', - help='Display sizes and dates in human readable format.') - parser.add_argument( - '--format', - choices=('json', 'table'), - help="Alter the output for a format like 'json' or 'table'." - " (default: table)") - parser.add_argument( - 'image', nargs='+', help='image for history report') - parser.set_defaults(class_=cls, method='history') - - def __init__(self, args): - """Construct History class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'ID', 12), - 'created': - ReportColumn('created', 'CREATED', 11), - 'createdBy': - ReportColumn('createdBy', 'CREATED BY', 45), - 'size': - ReportColumn('size', 'SIZE', 8), - 'comment': - ReportColumn('comment', 'COMMENT', 0) - }) - - def history(self): - """Report image history.""" - rows = list() - for ident in self._args.image: - for details in self.client.images.get(ident).history(): - fields = dict(details._asdict()) - - if self._args.human: - fields.update({ - 'size': - humanize.naturalsize(details.size), - 'created': - humanize.naturaldate( - podman.datetime_parse(details.created)), - }) - del fields['tags'] - - rows.append(fields) - - if self._args.quiet: - for row in rows: - ident = row['id'][:12] if self._args.truncate else row['id'] - print(ident) - elif self._args.format == 'json': - print(json.dumps(rows, indent=2), flush=True) - else: - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py deleted file mode 100644 index 21376eeeb..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/images_action.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Remote client commands dealing with images.""" -import operator -from collections import OrderedDict - -import humanize - -import podman -from pypodman.lib import AbstractActionBase, Report, ReportColumn - - -class Images(AbstractActionBase): - """Class for Image manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images commands to parent parser.""" - parser = parent.add_parser('images', help='list images') - super().subparser(parser) - parser.add_argument( - '--sort', - choices=['created', 'id', 'repository', 'size', 'tag'], - default='created', - type=str.lower, - help=('Change sort ordered of displayed images.' - ' (default: %(default)s)')) - - parser.add_flag( - '--digests', - help='Include digests with images.') - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Images class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'name': - ReportColumn('name', 'REPOSITORY', 0), - 'tag': - ReportColumn('tag', 'TAG', 10), - 'id': - ReportColumn('id', 'IMAGE ID', 12), - 'created': - ReportColumn('created', 'CREATED', 12), - 'size': - ReportColumn('size', 'SIZE', 8), - 'repoDigests': - ReportColumn('repoDigests', 'DIGESTS', 35), - }) - - def list(self): - """List images.""" - images = sorted( - self.client.images.list(), - key=operator.attrgetter(self._args.sort)) - if not images: - return - - rows = list() - for image in images: - fields = dict(image) - fields.update({ - 'created': - humanize.naturaldate(podman.datetime_parse(image.created)), - 'size': - humanize.naturalsize(int(image.size)), - 'repoDigests': - ' '.join(image.repoDigests), - }) - - for r in image.repoTags: - name, tag = r.rsplit(':', 1) - fields.update({ - 'name': name, - 'tag': tag, - }) - rows.append(fields) - - if not self._args.digests: - del self.columns['repoDigests'] - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/import_action.py b/contrib/python/pypodman/pypodman/lib/actions/import_action.py deleted file mode 100644 index 43448144a..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/import_action.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Remote client command to import tarball as image filesystem.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase, ChangeAction - - -class Import(AbstractActionBase): - """Class for importing tarball as image filesystem.""" - - @classmethod - def subparser(cls, parent): - """Add Import command to parent parser.""" - parser = parent.add_parser( - 'import', - help='import tarball as image filesystem', - ) - parser.add_argument( - '--change', - '-c', - action=ChangeAction, - ) - parser.add_argument( - '--message', - '-m', - help='Set commit message for imported image.', - ) - parser.add_argument( - 'source', - metavar='PATH', - nargs=1, - help='tarball to use as source on remote system', - ) - parser.add_argument( - 'reference', - metavar='TAG', - nargs='*', - help='Optional tag for image. (default: None)', - ) - parser.set_defaults(class_=cls, method='import_') - - def import_(self): - """Import tarball as image filesystem.""" - # ImportImage() validates it's parameters therefore we need to create - # pristine dict() for keywords - options = {} - if 'message' in self.opts: - options['message'] = self.opts['message'] - if 'change' in self.opts and self.opts['change']: - options['changes'] = self.opts['change'] - - reference = self.opts['reference'][0] if 'reference' in self.opts\ - else None - - try: - ident = self.client.images.import_image( - self.opts['source'][0], - reference, - **options, - ) - print(ident) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/info_action.py b/contrib/python/pypodman/pypodman/lib/actions/info_action.py deleted file mode 100644 index 3c854a358..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/info_action.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Remote client command for reporting on Podman service.""" -import json -import sys - -import podman -import yaml -from pypodman.lib import AbstractActionBase - - -class Info(AbstractActionBase): - """Class for reporting on Podman Service.""" - - @classmethod - def subparser(cls, parent): - """Add Info command to parent parser.""" - parser = parent.add_parser( - 'info', help='report info on podman service') - parser.add_argument( - '--format', - choices=('json', 'yaml'), - help="Alter the output for a format like 'json' or 'yaml'." - " (default: yaml)") - parser.set_defaults(class_=cls, method='info') - - def info(self): - """Report on Podman Service.""" - try: - info = self.client.system.info() - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - else: - if self._args.format == 'json': - print(json.dumps(info._asdict(), indent=2), flush=True) - else: - print( - yaml.dump( - dict(info._asdict()), - canonical=False, - default_flow_style=False), - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py deleted file mode 100644 index ca5ad2215..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Remote client command for inspecting podman objects.""" -import json -import logging -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Inspect(AbstractActionBase): - """Class for inspecting podman objects.""" - - @classmethod - def subparser(cls, parent): - """Add Inspect command to parent parser.""" - parser = parent.add_parser('inspect', help='inspect objects') - parser.add_argument( - '--type', - '-t', - choices=('all', 'container', 'image'), - default='all', - type=str.lower, - help='Type of object to inspect', - ) - parser.add_flag( - '--size', - help='Display the total file size if the type is a container.') - parser.add_argument( - 'objects', - nargs='+', - help='objects to inspect', - ) - parser.set_defaults(class_=cls, method='inspect') - - def _get_container(self, ident): - try: - logging.debug("Getting container %s", ident) - ctnr = self.client.containers.get(ident) - except podman.ContainerNotFound: - pass - else: - return ctnr.inspect() - - def _get_image(self, ident): - try: - logging.debug("Getting image %s", ident) - img = self.client.images.get(ident) - except podman.ImageNotFound: - pass - else: - return img.inspect() - - def inspect(self): - """Inspect provided podman objects.""" - output = [] - try: - for ident in self._args.objects: - obj = None - - if self._args.type in ('all', 'container'): - obj = self._get_container(ident) - if obj is None and self._args.type in ('all', 'image'): - obj = self._get_image(ident) - - if obj is None: - if self._args.type == 'container': - msg = 'Container "{}" not found'.format(ident) - elif self._args.type == 'image': - msg = 'Image "{}" not found'.format(ident) - else: - msg = 'Object "{}" not found'.format(ident) - print(msg, file=sys.stderr, flush=True) - else: - fields = obj._asdict() - if not self._args.size: - try: - del fields['sizerootfs'] - except KeyError: - pass - output.append(fields) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - else: - print(json.dumps(output, indent=2)) diff --git a/contrib/python/pypodman/pypodman/lib/actions/kill_action.py b/contrib/python/pypodman/pypodman/lib/actions/kill_action.py deleted file mode 100644 index e8fb4e74d..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/kill_action.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Remote client command for signaling podman containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase, SignalAction - - -class Kill(AbstractActionBase): - """Class for sending signal to main process in container.""" - - @classmethod - def subparser(cls, parent): - """Add Kill command to parent parser.""" - parser = parent.add_parser('kill', help='signal container') - parser.add_argument( - '--signal', - '-s', - action=SignalAction, - default=9, - help='Signal to send to the container. (default: %(default)s)') - parser.add_argument( - 'containers', - nargs='+', - help='containers to signal', - ) - parser.set_defaults(class_=cls, method='kill') - - def kill(self): - """Signal provided containers.""" - try: - for ident in self._args.containers: - try: - ctnr = self.client.containers.get(ident) - ctnr.kill(self._args.signal) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - else: - print(ident) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/logs_action.py b/contrib/python/pypodman/pypodman/lib/actions/logs_action.py deleted file mode 100644 index 91ff7bb08..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/logs_action.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Remote client command for retrieving container logs.""" -import argparse -import logging -import sys -from collections import deque - -import podman -from pypodman.lib import AbstractActionBase, PositiveIntAction - - -class Logs(AbstractActionBase): - """Class for retrieving logs from container.""" - - @classmethod - def subparser(cls, parent): - """Add Logs command to parent parser.""" - parser = parent.add_parser('logs', help='retrieve logs from container') - parser.add_argument( - '--tail', - metavar='LINES', - action=PositiveIntAction, - help='Output the specified number of LINES at the end of the logs') - parser.add_argument( - 'container', - nargs=1, - help='retrieve container logs', - ) - parser.set_defaults(class_=cls, method='logs') - - def __init__(self, args): - """Construct Logs class.""" - super().__init__(args) - - def logs(self): - """Retrieve logs from containers.""" - try: - ident = self._args.container[0] - try: - logging.debug('Get container "%s" logs', ident) - ctnr = self.client.containers.get(ident) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - else: - if self._args.tail: - logs = iter(deque(ctnr.logs(), maxlen=self._args.tail)) - else: - logs = ctnr.logs() - - for line in logs: - sys.stdout.write(line) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/mount_action.py b/contrib/python/pypodman/pypodman/lib/actions/mount_action.py deleted file mode 100644 index 905eda6da..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/mount_action.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Remote client command for retrieving mounts from containers.""" -import sys -from collections import OrderedDict - -import podman -from pypodman.lib import AbstractActionBase, Report, ReportColumn - - -class Mount(AbstractActionBase): - """Class for retrieving mounts from container.""" - - @classmethod - def subparser(cls, parent): - """Add mount command to parent parser.""" - parser = parent.add_parser( - 'mount', help='retrieve mounts from containers.') - super().subparser(parser) - parser.add_argument( - 'containers', - nargs='*', - help='containers to list ports', - ) - parser.set_defaults(class_=cls, method='mount') - - def __init__(self, args): - """Construct Mount class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'CONTAINER ID', 14), - 'destination': - ReportColumn('destination', 'DESTINATION', 0) - }) - - def mount(self): - """Retrieve mounts from containers.""" - try: - ctnrs = [] - if not self._args.containers: - ctnrs = self.client.containers.list() - else: - for ident in self._args.containers: - try: - ctnrs.append(self.client.containers.get(ident)) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - - if not ctnrs: - print( - 'Unable to find any containers.', file=sys.stderr, flush=True) - return 1 - - rows = list() - for ctnr in ctnrs: - details = ctnr.inspect() - rows.append({ - 'id': ctnr.id, - 'destination': details.graphdriver['data']['mergeddir'] - }) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/pause_action.py b/contrib/python/pypodman/pypodman/lib/actions/pause_action.py deleted file mode 100644 index 7dc02f7fe..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pause_action.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Remote client command for pausing processes in containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Pause(AbstractActionBase): - """Class for pausing processes in container.""" - - @classmethod - def subparser(cls, parent): - """Add Pause command to parent parser.""" - parser = parent.add_parser('pause', help='pause container processes') - parser.add_argument( - 'containers', - nargs='+', - help='containers to pause', - ) - parser.set_defaults(class_=cls, method='pause') - - def pause(self): - """Pause provided containers.""" - try: - for ident in self._args.containers: - try: - ctnr = self.client.containers.get(ident) - ctnr.pause() - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - else: - print(ident) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/__init__.py b/contrib/python/pypodman/pypodman/lib/actions/pod/__init__.py deleted file mode 100644 index 91c54f417..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Provide subparsers for pod commands.""" -from pypodman.lib.actions.pod.create_parser import CreatePod -from pypodman.lib.actions.pod.inspect_parser import InspectPod -from pypodman.lib.actions.pod.kill_parser import KillPod -from pypodman.lib.actions.pod.pause_parser import PausePod -from pypodman.lib.actions.pod.processes_parser import ProcessesPod -from pypodman.lib.actions.pod.remove_parser import RemovePod -from pypodman.lib.actions.pod.start_parser import StartPod -from pypodman.lib.actions.pod.stop_parser import StopPod -from pypodman.lib.actions.pod.top_parser import TopPod -from pypodman.lib.actions.pod.unpause_parser import UnpausePod - -__all__ = [ - 'CreatePod', - 'InspectPod', - 'KillPod', - 'PausePod', - 'ProcessesPod', - 'RemovePod', - 'StartPod', - 'StopPod', - 'TopPod', - 'UnpausePod', -] diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py deleted file mode 100644 index 4e0bde777..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Remote client command for creating pod.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class CreatePod(AbstractActionBase): - """Implement Create Pod command.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Create command to parent parser.""" - parser = parent.add_parser('create', help='create pod') - super().subparser(parser) - - parser.add_argument( - '--cgroup-parent', - dest='cgroupparent', - type=str, - help='Path to cgroups under which the' - ' cgroup for the pod will be created.') - parser.add_flag( - '--infra', - help='Create an infra container and associate it with the pod.') - parser.add_argument( - '-l', - '--label', - dest='labels', - action='append', - type=str, - help='Add metadata to a pod (e.g., --label=com.example.key=value)') - parser.add_argument( - '-n', - '--name', - dest='ident', - type=str, - help='Assign name to the pod') - parser.add_argument( - '--share', - choices=('ipc', 'net', 'pid', 'user', 'uts'), - help='Comma deliminated list of kernel namespaces to share') - - parser.set_defaults(class_=cls, method='create') - - # TODO: Add golang CLI arguments not included in API. - # parser.add_argument( - # '--infra-command', - # default='/pause', - # help='Command to run to start the infra container.' - # '(default: %(default)s)') - # parser.add_argument( - # '--infra-image', - # default='k8s.gcr.io/pause:3.1', - # help='Image to create for the infra container.' - # '(default: %(default)s)') - # parser.add_argument( - # '--podidfile', - # help='Write the pod ID to given file name on remote host') - - def create(self): - """Create Pod from given options.""" - config = {} - for key in ('ident', 'cgroupparent', 'infra', 'labels', 'share'): - config[key] = self.opts.get(key) - - try: - pod = self.client.pods.create(**config) - except podman.ErrorOccurred as ex: - sys.stdout.flush() - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - else: - print(pod.id) diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/inspect_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/inspect_parser.py deleted file mode 100644 index 3c42d636c..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/inspect_parser.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Remote client command for inspecting pods.""" -import json -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class InspectPod(AbstractActionBase): - """Class for reporting on pods and their containers.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Inspect command to parent parser.""" - parser = parent.add_parser( - 'inspect', - help='configuration and state information about a given pod') - parser.add_argument('pod', nargs='+', help='pod(s) to inspect') - parser.set_defaults(class_=cls, method='inspect') - - def inspect(self): - """Report on provided pods.""" - output = {} - try: - for ident in self._args.pod: - try: - pod = self.client.pods.get(ident) - except podman.PodNotFound: - print( - 'Pod "{}" not found.'.format(ident), - file=sys.stdout, - flush=True) - output.update(pod.inspect()._asdict()) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - else: - print(json.dumps(output, indent=2)) - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py deleted file mode 100644 index 9b6229939..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Remote client command for signaling pods and their containers.""" -import signal -import sys - -import podman -from pypodman.lib import AbstractActionBase, SignalAction -from pypodman.lib import query_model as query_pods - - -class KillPod(AbstractActionBase): - """Class for sending signal to processes in pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Kill command to parent parser.""" - parser = parent.add_parser('kill', help='signal containers in pod') - - parser.add_flag( - '--all', - '-a', - help='Sends signal to all pods.') - parser.add_argument( - '-s', - '--signal', - action=SignalAction, - default=9, - help='Signal to send to the pod. (default: %(default)s)') - parser.add_argument('pod', nargs='*', help='pod(s) to signal') - parser.set_defaults(class_=cls, method='kill') - - def __init__(self, args): - """Construct Pod Kill object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, but not both') - super().__init__(args) - - def kill(self): - """Signal provided pods.""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.kill(self._args.signal) - print(pod.id) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found.'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py deleted file mode 100644 index c751314ca..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Remote client command for pausing processes in pod.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class PausePod(AbstractActionBase): - """Class for pausing containers in pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Pause command to parent parser.""" - parser = parent.add_parser('pause', help='pause containers in pod') - parser.add_flag( - '--all', - '-a', - help='Pause all pods.') - parser.add_argument('pod', nargs='*', help='pod(s) to pause.') - parser.set_defaults(class_=cls, method='pause') - - def __init__(self, args): - """Construct Pod Pause object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, but not both') - super().__init__(args) - - def pause(self): - """Pause containers in provided Pod.""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.pause() - print(pod.id) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py deleted file mode 100644 index 855e313c7..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Report on pod's containers' processes.""" -import operator -from collections import OrderedDict - -from pypodman.lib import AbstractActionBase, Report, ReportColumn - - -class ProcessesPod(AbstractActionBase): - """Report on Pod's processes.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Ps command to parent parser.""" - parser = parent.add_parser('ps', help='list processes of pod') - super().subparser(parser) - - parser.add_flag( - '--ctr-names', - help='Include container name in the info field.') - parser.add_flag( - '--ctr-ids', - help='Include container ID in the info field.') - parser.add_flag( - '--ctr-status', - help='Include container status in the info field.') - parser.add_argument( - '--format', - choices=('json'), - help='Pretty-print containers to JSON') - parser.add_argument( - '--sort', - choices=('created', 'id', 'name', 'status', 'count'), - default='created', - type=str.lower, - help='Sort on given field. (default: %(default)s)') - parser.add_argument('--filter', help='Not Implemented') - parser.set_defaults(class_=cls, method='processes') - - def __init__(self, args): - """Construct ProcessesPod class.""" - if args.sort == 'created': - args.sort = 'createdat' - elif args.sort == 'count': - args.sort = 'numberofcontainers' - - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'POD ID', 14), - 'name': - ReportColumn('name', 'NAME', 30), - 'status': - ReportColumn('status', 'STATUS', 8), - 'numberofcontainers': - ReportColumn('numberofcontainers', 'NUMBER OF CONTAINERS', 0), - 'info': - ReportColumn('info', 'CONTAINER INFO', 0), - }) - - def processes(self): - """List pods.""" - pods = sorted( - self.client.pods.list(), key=operator.attrgetter(self._args.sort)) - if not pods: - return - - rows = list() - for pod in pods: - fields = dict(pod) - if self._args.ctr_ids \ - or self._args.ctr_names \ - or self._args.ctr_status: - keys = ('id', 'name', 'status', 'info') - info = [] - for ctnr in pod.containersinfo: - ctnr_info = [] - if self._args.ctr_ids: - ctnr_info.append(ctnr['id']) - if self._args.ctr_names: - ctnr_info.append(ctnr['name']) - if self._args.ctr_status: - ctnr_info.append(ctnr['status']) - info.append("[ {} ]".format(" ".join(ctnr_info))) - fields.update({'info': " ".join(info)}) - else: - keys = ('id', 'name', 'status', 'numberofcontainers') - - rows.append(fields) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout(rows, keys, truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py deleted file mode 100644 index 289325d14..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Remote client command for deleting pod and containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class RemovePod(AbstractActionBase): - """Class for removing pod and containers from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Rm command to parent parser.""" - parser = parent.add_parser('rm', help='Delete pod and container(s)') - parser.add_flag( - '--all', - '-a', - help='Remove all pods.') - parser.add_flag( - '--force', - '-f', - help='Stop and remove container(s) then delete pod.') - parser.add_argument( - 'pod', nargs='*', help='Pod to remove. Or, use --all') - parser.set_defaults(class_=cls, method='remove') - - def __init__(self, args): - """Construct RemovePod object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, but not both') - super().__init__(args) - - def remove(self): - """Remove pod and container(s).""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.remove(self._args.force) - print(pod.id) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found.'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize, - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py deleted file mode 100644 index 53f45b6de..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Remote client command for restarting pod and container(s).""" -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class RestartPod(AbstractActionBase): - """Class for restarting containers in Pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Restart command to parent parser.""" - parser = parent.add_parser('restart', help='restart containers in pod') - parser.add_flag( - '--all', - '-a', - help='Restart all pods.') - parser.add_argument( - 'pod', nargs='*', help='Pod to restart. Or, use --all') - parser.set_defaults(class_=cls, method='restart') - - def __init__(self, args): - """Construct RestartPod object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, not both') - super().__init__(args) - - def restart(self): - """Restart pod and container(s).""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.restart() - print(pod.id) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found.'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py deleted file mode 100644 index ff62b839e..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Remote client command for starting pod and container(s).""" - -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class StartPod(AbstractActionBase): - """Class for starting pod and container(s).""" - - @classmethod - def subparser(cls, parent): - """Add Pod Start command to parent parser.""" - parser = parent.add_parser('start', help='start pod') - parser.add_flag( - '--all', - '-a', - help='Start all pods.') - parser.add_argument( - 'pod', nargs='*', help='Pod to start. Or, use --all') - parser.set_defaults(class_=cls, method='start') - - def __init__(self, args): - """Construct StartPod object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, but not both') - super().__init__(args) - - def start(self): - """Start pod and container(s).""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.start() - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py deleted file mode 100644 index cbf2bf1e7..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Remote client command for stopping pod and container(s).""" -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class StopPod(AbstractActionBase): - """Class for stopping pod and container(s).""" - - @classmethod - def subparser(cls, parent): - """Add Pod Stop command to parent parser.""" - parser = parent.add_parser('stop', help='stop pod') - parser.add_flag( - '--all', - '-a', - help='Stop all pods.') - parser.add_argument( - 'pod', nargs='*', help='Pod to stop. Or, use --all') - parser.set_defaults(class_=cls, method='stop') - - def __init__(self, args): - """Contruct StopPod object.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, not both') - super().__init__(args) - - def stop(self): - """Stop pod and container(s).""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.stop() - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/top_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/top_parser.py deleted file mode 100644 index f27d60f14..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/top_parser.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Remote client command for reporting on pod and container(s).""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class TopPod(AbstractActionBase): - """Report on containers in Pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Top command to parent parser.""" - parser = parent.add_parser('top', help='report on containers in pod') - parser.add_argument('pod', nargs=1, help='Pod to report on.') - parser.set_defaults(class_=cls, method='top') - - def top(self): - """Report on pod and container(s).""" - try: - for ident in self._args.pod: - pod = self.client.pods.get(ident) - print(pod.top()) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found.'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py deleted file mode 100644 index 5186cf9cc..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Remote client command for unpausing processes in pod.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase -from pypodman.lib import query_model as query_pods - - -class UnpausePod(AbstractActionBase): - """Class for unpausing containers in pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Unpause command to parent parser.""" - parser = parent.add_parser('unpause', help='unpause pod') - parser.add_flag( - '--all', - '-a', - help='Unpause all pods.') - parser.add_argument( - 'pod', nargs='*', help='Pod to unpause. Or, use --all') - parser.set_defaults(class_=cls, method='unpause') - - def __init__(self, args): - """Construct Pod Unpause class.""" - if args.all and args.pod: - raise ValueError('You may give a pod or use --all, but not both') - super().__init__(args) - - def unpause(self): - """Unpause containers in provided Pod.""" - idents = None if self._args.all else self._args.pod - pods = query_pods(self.client.pods, idents) - - for pod in pods: - try: - pod.unpause() - print(pod.id) - except podman.PodNotFound as ex: - print( - 'Pod "{}" not found'.format(ex.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as ex: - print( - '{}'.format(ex.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod_action.py b/contrib/python/pypodman/pypodman/lib/actions/pod_action.py deleted file mode 100644 index 4b8997a05..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pod_action.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Remote client command for pod subcommands.""" -import inspect -import logging -import sys - -from pypodman.lib import AbstractActionBase - -# pylint: disable=wildcard-import -# pylint: disable=unused-wildcard-import -from .pod import * - - -class Pod(AbstractActionBase): - """Class for creating a pod.""" - - @classmethod - def subparser(cls, parent): - """Add Pod Create command to parent parser.""" - pod_parser = parent.add_parser( - 'pod', - help='pod commands.' - ' For subcommands, see: %(prog)s pod --help') - subparser = pod_parser.add_subparsers() - - # pull in plugin(s) code for each subcommand - for name, obj in inspect.getmembers( - sys.modules['pypodman.lib.actions.pod'], - predicate=inspect.isclass): - if hasattr(obj, 'subparser'): - try: - obj.subparser(subparser) - except NameError as e: - logging.critical(e) - logging.warning( - 'See subparser configuration for Class "%s"', name) - sys.exit(3) diff --git a/contrib/python/pypodman/pypodman/lib/actions/port_action.py b/contrib/python/pypodman/pypodman/lib/actions/port_action.py deleted file mode 100644 index 6913f3813..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/port_action.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Remote client command for retrieving ports from containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Port(AbstractActionBase): - """Class for retrieving ports from container.""" - - @classmethod - def subparser(cls, parent): - """Add Port command to parent parser.""" - parser = parent.add_parser( - 'port', help='retrieve ports from containers') - parser.add_flag( - '--all', - '-a', - help='List all known port mappings for running containers') - parser.add_argument( - 'containers', - nargs='*', - help='containers to list ports', - ) - parser.set_defaults(class_=cls, method='port') - - def __init__(self, args): - """Construct Port class.""" - if not args.all and not args.containers: - raise ValueError('You must supply at least one' - ' container id or name, or --all.') - super().__init__(args) - - def port(self): - """Retrieve ports from containers.""" - try: - ctnrs = [] - if self._args.all: - ctnrs = self.client.containers.list() - else: - for ident in self._args.containers: - try: - ctnrs.append(self.client.containers.get(ident)) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - - for ctnr in ctnrs: - print("{}\n{}".format(ctnr.id, ctnr.ports)) - - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py deleted file mode 100644 index 62ceb2e67..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Remote client commands dealing with containers.""" -import operator -from collections import OrderedDict - -import humanize - -import podman -from pypodman.lib import AbstractActionBase, Report, ReportColumn - - -class Ps(AbstractActionBase): - """Class for Container manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images command to parent parser.""" - parser = parent.add_parser('ps', help='list containers') - super().subparser(parser) - - parser.add_argument( - '--sort', - choices=('createdat', 'id', 'image', 'names', 'runningfor', 'size', - 'status'), - default='createdat', - type=str.lower, - help=('Change sort ordered of displayed containers.' - ' (default: %(default)s)')) - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Ps class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'CONTAINER ID', 12), - 'image': - ReportColumn('image', 'IMAGE', 31), - 'command': - ReportColumn('column', 'COMMAND', 20), - 'createdat': - ReportColumn('createdat', 'CREATED', 12), - 'status': - ReportColumn('status', 'STATUS', 10), - 'ports': - ReportColumn('ports', 'PORTS', 0), - 'names': - ReportColumn('names', 'NAMES', 18) - }) - - def list(self): - """List containers.""" - if self._args.all: - ictnrs = self.client.containers.list() - else: - ictnrs = filter( - lambda c: podman.FoldedString(c['status']) == 'running', - self.client.containers.list()) - - # TODO: Verify sorting on dates and size - ctnrs = sorted(ictnrs, key=operator.attrgetter(self._args.sort)) - if not ctnrs: - return - - rows = list() - for ctnr in ctnrs: - fields = dict(ctnr) - fields.update({ - 'command': - ' '.join(ctnr.command), - 'createdat': - humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), - }) - rows.append(fields) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/pull_action.py b/contrib/python/pypodman/pypodman/lib/actions/pull_action.py deleted file mode 100644 index d8fbfc1f0..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/pull_action.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Remote client command for pulling images.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Pull(AbstractActionBase): - """Class for retrieving images from repository.""" - - @classmethod - def subparser(cls, parent): - """Add Pull command to parent parser.""" - parser = parent.add_parser( - 'pull', - help='retrieve image from repository', - ) - parser.add_argument( - 'targets', - nargs='+', - help='image id(s) to retrieve.', - ) - parser.set_defaults(class_=cls, method='pull') - - def __init__(self, args): - """Construct Pull class.""" - super().__init__(args) - - def pull(self): - """Retrieve image.""" - for ident in self._args.targets: - try: - self.client.images.pull(ident) - print(ident) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/push_action.py b/contrib/python/pypodman/pypodman/lib/actions/push_action.py deleted file mode 100644 index 8e86ca335..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/push_action.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Remote client command for pushing image elsewhere.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Push(AbstractActionBase): - """Class for pushing images to repository.""" - - @classmethod - def subparser(cls, parent): - """Add Push command to parent parser.""" - parser = parent.add_parser( - 'push', - help='push image elsewhere', - ) - parser.add_flag( - '--tlsverify', - help='Require HTTPS and verify certificates when' - ' contacting registries.') - parser.add_argument( - 'image', nargs=1, help='name or id of image to push') - parser.add_argument( - 'tag', - nargs=1, - help='destination image id', - ) - parser.set_defaults(class_=cls, method='push') - - def pull(self): - """Store image elsewhere.""" - try: - try: - img = self.client.images.get(self._args.image[0]) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - else: - img.push(self._args.tag[0], tlsverify=self._args.tlsverify) - print(self._args.image[0]) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/restart_action.py b/contrib/python/pypodman/pypodman/lib/actions/restart_action.py deleted file mode 100644 index 415594920..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/restart_action.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Remote client command for restarting containers.""" -import logging -import sys - -import podman -from pypodman.lib import AbstractActionBase, PositiveIntAction - - -class Restart(AbstractActionBase): - """Class for Restarting containers.""" - - @classmethod - def subparser(cls, parent): - """Add Restart command to parent parser.""" - parser = parent.add_parser('restart', help='restart container(s)') - parser.add_argument( - '--timeout', - action=PositiveIntAction, - default=10, - help='Timeout to wait before forcibly stopping the container' - ' (default: %(default)s seconds)') - parser.add_argument( - 'targets', nargs='+', help='container id(s) to restart') - parser.set_defaults(class_=cls, method='restart') - - def restart(self): - """Restart container(s).""" - try: - for ident in self._args.targets: - try: - ctnr = self.client.containers.get(ident) - logging.debug('Restarting Container %s', ctnr.id) - ctnr.restart(timeout=self._args.timeout) - print(ident) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/rm_action.py b/contrib/python/pypodman/pypodman/lib/actions/rm_action.py deleted file mode 100644 index 99ff6c460..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/rm_action.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Remote client command for deleting containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Rm(AbstractActionBase): - """Class for removing containers from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rm command to parent parser.""" - parser = parent.add_parser('rm', help='delete container(s)') - parser.add_flag( - '--force', - '-f', - help='force delete of running container(s).') - parser.add_argument( - 'targets', nargs='+', help='container id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def remove(self): - """Remove container(s).""" - for ident in self._args.targets: - try: - ctnr = self.client.containers.get(ident) - ctnr.remove(self._args.force) - print(ident) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py b/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py deleted file mode 100644 index 7c3d0bd79..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Remote client command for deleting images.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Rmi(AbstractActionBase): - """Class for removing images from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rmi command to parent parser.""" - parser = parent.add_parser('rmi', help='delete image(s)') - parser.add_flag( - '--force', - '-f', - help='force delete of image(s) and associated containers.') - parser.add_argument('targets', nargs='+', help='image id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def remove(self): - """Remove image(s).""" - for ident in self._args.targets: - try: - img = self.client.images.get(ident) - img.remove(self._args.force) - print(ident) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/run_action.py b/contrib/python/pypodman/pypodman/lib/actions/run_action.py deleted file mode 100644 index 6a6b3cb2c..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/run_action.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Remote client command for run a command in a new container.""" -import logging -import sys - -import podman -from pypodman.lib import AbstractActionBase - -from ._create_args import CreateArguments - - -class Run(AbstractActionBase): - """Class for running a command in a container.""" - - @classmethod - def subparser(cls, parent): - """Add Run command to parent parser.""" - parser = parent.add_parser('run', help='Run container from image') - - CreateArguments.add_arguments(parser) - - parser.add_argument('image', nargs=1, help='source image id.') - parser.add_argument( - 'command', - nargs=parent.REMAINDER, - help='command and args to run.', - ) - parser.set_defaults(class_=cls, method='run') - - def __init__(self, args): - """Construct Run class.""" - super().__init__(args) - if args.detach and args.rm: - raise ValueError('Incompatible options: --detach and --rm') - - # image id used only on client - del self.opts['image'] - - def run(self): - """Run container.""" - for ident in self._args.image: - try: - try: - img = self.client.images.get(ident) - ctnr = img.container(**self.opts) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - continue - else: - logging.debug('New container created "{}"'.format(ctnr.id)) - - if self._args.detach: - ctnr.start() - print(ctnr.id) - else: - ctnr.attach(eot=4) - ctnr.start() - print(ctnr.id) - - if self._args.rm: - ctnr.remove(force=True) - except (BrokenPipeError, KeyboardInterrupt): - print('\nContainer "{}" disconnected.'.format(ctnr.id)) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - 'Run for container "{}" failed: {} {}'.format( - ctnr.id, repr(e), e.reason.capitalize()), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/search_action.py b/contrib/python/pypodman/pypodman/lib/actions/search_action.py deleted file mode 100644 index b7b8b465d..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/search_action.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Remote client command for searching registries for an image.""" -import argparse -import sys -from collections import OrderedDict - -import podman -from pypodman.lib import (AbstractActionBase, PositiveIntAction, Report, - ReportColumn) - - -class FilterAction(argparse.Action): - """Parse filter argument components.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar='FILTER'): - """Create FilterAction object.""" - help = (help or '') + (' (format: stars=##' - ' or is-automated=[True|False]' - ' or is-official=[True|False])') - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - """ - Convert and Validate input. - - Note: side effects - 1) self.dest value is set to subargument dest - 2) new attribute self.dest + '_value' is created with 2nd value. - """ - opt, val = values.split('=', 1) - if opt == 'stars': - msg = ('{} option "stars" requires' - ' a positive integer').format(self.dest) - try: - val = int(val) - except ValueError: - parser.error(msg) - - if val < 0: - parser.error(msg) - elif opt == 'is-automated': - if val.capitalize() in ('True', 'False'): - val = bool(val) - else: - msg = ('{} option "is-automated"' - ' must be True or False.'.format(self.dest)) - parser.error(msg) - elif opt == 'is-official': - if val.capitalize() in ('True', 'False'): - val = bool(val) - else: - msg = ('{} option "is-official"' - ' must be True or False.'.format(self.dest)) - parser.error(msg) - else: - msg = ('{} only supports one of the following options:\n' - ' stars, is-automated, or is-official').format(self.dest) - parser.error(msg) - setattr(namespace, self.dest, opt) - setattr(namespace, self.dest + '_value', val) - - -class Search(AbstractActionBase): - """Class for searching registries for an image.""" - - @classmethod - def subparser(cls, parent): - """Add Search command to parent parser.""" - parser = parent.add_parser('search', help='search for images') - super().subparser(parser) - parser.add_argument( - '--filter', - '-f', - action=FilterAction, - help='Filter output based on conditions provided.') - parser.add_argument( - '--limit', - action=PositiveIntAction, - default=25, - help='Limit the number of results.' - ' (default: %(default)s)') - parser.add_argument('term', nargs=1, help='search term for image') - parser.set_defaults(class_=cls, method='search') - - def __init__(self, args): - """Construct Search class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'name': - ReportColumn('name', 'NAME', 44), - 'description': - ReportColumn('description', 'DESCRIPTION', 44), - 'star_count': - ReportColumn('star_count', 'STARS', 5), - 'is_official': - ReportColumn('is_official', 'OFFICIAL', 8), - 'is_automated': - ReportColumn('is_automated', 'AUTOMATED', 9), - }) - - def search(self): - """Search registries for image.""" - try: - rows = list() - for entry in self.client.images.search( - self._args.term[0], limit=self._args.limit): - - if self._args.filter == 'is-official': - if self._args.filter_value != entry.is_official: - continue - elif self._args.filter == 'is-automated': - if self._args.filter_value != entry.is_automated: - continue - elif self._args.filter == 'stars': - if self._args.filter_value > entry.star_count: - continue - - fields = dict(entry._asdict()) - - status = '[OK]' if entry.is_official else '' - fields['is_official'] = status - - status = '[OK]' if entry.is_automated else '' - fields['is_automated'] = status - - if self._args.truncate: - fields.update({'name': entry.name[-44:]}) - rows.append(fields) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/start_action.py b/contrib/python/pypodman/pypodman/lib/actions/start_action.py deleted file mode 100644 index 5f88731dc..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/start_action.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Remote client command for starting containers.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Start(AbstractActionBase): - """Class for starting container.""" - - @classmethod - def subparser(cls, parent): - """Add Start command to parent parser.""" - parser = parent.add_parser('start', help='start container') - parser.add_flag( - '--attach', - '-a', - help="Attach container's STDOUT and STDERR.") - parser.add_argument( - '--detach-keys', - metavar='KEY(s)', - default=4, - help='Override the key sequence for detaching a container.' - ' (format: a single character [a-Z] or ctrl-<value> where' - ' <value> is one of: a-z, @, ^, [, , or _) (default: ^D)') - parser.add_flag( - '--interactive', - '-i', - help="Attach container's STDIN.") - # TODO: Implement sig-proxy - parser.add_flag( - '--sig-proxy', - help="Proxy received signals to the process." - ) - parser.add_argument( - 'containers', - nargs='+', - help='containers to start', - ) - parser.set_defaults(class_=cls, method='start') - - def start(self): - """Start provided containers.""" - stdin = sys.stdin if self.opts['interactive'] else None - stdout = sys.stdout if self.opts['attach'] else None - - try: - for ident in self._args.containers: - try: - ctnr = self.client.containers.get(ident) - ctnr.attach( - eot=self.opts['detach_keys'], - stdin=stdin, - stdout=stdout) - ctnr.start() - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container "{}" not found'.format(e.name), - file=sys.stderr, - flush=True) - else: - print(ident) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/version_action.py b/contrib/python/pypodman/pypodman/lib/actions/version_action.py deleted file mode 100644 index 29a0cabe4..000000000 --- a/contrib/python/pypodman/pypodman/lib/actions/version_action.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Remote client command for reporting on Podman service.""" -import sys - -import podman -from pypodman.lib import AbstractActionBase - - -class Version(AbstractActionBase): - """Class for reporting on Podman Service.""" - - @classmethod - def subparser(cls, parent): - """Add Version command to parent parser.""" - parser = parent.add_parser( - 'version', help='report version on podman service') - parser.set_defaults(class_=cls, method='version') - - def version(self): - """Report on Podman Service.""" - try: - info = self.client.system.info() - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) - return 1 - else: - version = info._asdict()['podman'] - host = info._asdict()['host'] - print("Version {}".format(version['podman_version'])) - print("Go Version {}".format(version['go_version'])) - print("Git Commit {}".format(version['git_commit'])) - print("OS/Arch {}/{}".format(host["os"], host["arch"])) diff --git a/contrib/python/pypodman/pypodman/lib/parser_actions.py b/contrib/python/pypodman/pypodman/lib/parser_actions.py deleted file mode 100644 index 3ff12cab8..000000000 --- a/contrib/python/pypodman/pypodman/lib/parser_actions.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Supplimental argparse.Action converters and validaters. - -The constructors are very verbose but remain for IDE support. -""" -import argparse -import copy -import os -import signal - -# API defined by argparse.Action therefore shut up pylint -# pragma pylint: disable=redefined-builtin -# pragma pylint: disable=too-few-public-methods -# pragma pylint: disable=too-many-arguments - - -class ChangeAction(argparse.Action): - """Convert and validate change argument.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar='OPT=VALUE'): - """Create ChangeAction object.""" - help = (help or '') + ('Apply change(s) to the new image.' - ' May be given multiple times.') - if default is None: - default = [] - - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - """Convert and Validate input.""" - items = getattr(namespace, self.dest, None) or [] - items = copy.copy(items) - - choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') - - opt, _ = values.split('=', 1) - if opt not in choices: - parser.error('Option "{}" is not supported by argument "{}",' - ' valid options are: {}'.format( - opt, option_string, ', '.join(choices))) - items.append(values) - setattr(namespace, self.dest, items) - - -class SignalAction(argparse.Action): - """Validate input as a signal.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=str, - choices=None, - required=False, - help='The signal to send.' - ' It may be given as a name or a number.', - metavar='SIGNAL'): - """Create SignalAction object.""" - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - if hasattr(signal, "Signals"): - - def _signal_number(signame): - cooked = 'SIG{}'.format(signame) - try: - return signal.Signals[cooked].value - except ValueError: - pass - else: - - def _signal_number(signame): - cooked = 'SIG{}'.format(signame) - for n, v in sorted(signal.__dict__.items()): - if n != cooked: - continue - if n.startswith("SIG") and not n.startswith("SIG_"): - return v - - self._signal_number = _signal_number - - def __call__(self, parser, namespace, values, option_string=None): - """Validate input is a signal for platform.""" - if values.isdigit(): - signum = int(values) - if signal.SIGRTMIN <= signum >= signal.SIGRTMAX: - raise ValueError('"{}" is not a valid signal. {}-{}'.format( - values, signal.SIGRTMIN, signal.SIGRTMAX)) - else: - signum = self._signal_number(values) - if signum is None: - parser.error( - '"{}" is not a valid signal,' - ' see your platform documentation.'.format(values)) - setattr(namespace, self.dest, signum) - - -class UnitAction(argparse.Action): - """Validate number given is positive integer, with optional suffix.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar='UNIT'): - """Create UnitAction object.""" - help = (help or metavar or dest)\ - + ' (format: <number>[<unit>], where unit = b, k, m or g)' - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - """Validate input as a UNIT.""" - try: - val = int(values) - except ValueError: - if not values[:-1].isdigit(): - msg = ('{} must be a positive integer,' - ' with optional suffix').format(option_string) - parser.error(msg) - if not values[-1] in ('b', 'k', 'm', 'g'): - msg = '{} only supports suffices of: b, k, m, g'.format( - option_string) - parser.error(msg) - else: - if val <= 0: - msg = '{} must be a positive integer'.format(option_string) - parser.error(msg) - - setattr(namespace, self.dest, values) - - -class PositiveIntAction(argparse.Action): - """Validate number given is positive integer.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=int, - choices=None, - required=False, - help='Must be a positive integer.', - metavar=None): - """Create PositiveIntAction object.""" - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - """Validate input.""" - if values > 0: - setattr(namespace, self.dest, values) - return - - msg = '{} must be a positive integer'.format(option_string) - parser.error(msg) - - -class PathAction(argparse.Action): - """Expand user- and relative-paths.""" - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar='PATH'): - """Create PathAction object.""" - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - """Resolve full path value on local filesystem.""" - setattr(namespace, self.dest, - os.path.abspath(os.path.expanduser(values))) diff --git a/contrib/python/pypodman/pypodman/lib/podman_parser.py b/contrib/python/pypodman/pypodman/lib/podman_parser.py deleted file mode 100644 index 913546a91..000000000 --- a/contrib/python/pypodman/pypodman/lib/podman_parser.py +++ /dev/null @@ -1,255 +0,0 @@ -"""Parse configuration while building subcommands.""" -import argparse -import getpass -import inspect -import logging -import os -import shutil -import sys -from contextlib import suppress -from pathlib import Path - -import pkg_resources -import pytoml - -from .parser_actions import PathAction, PositiveIntAction - -# TODO: setup.py and obtain __version__ from rpm.spec -try: - __version__ = pkg_resources.get_distribution('pypodman').version -except Exception: # pylint: disable=broad-except - __version__ = '0.0.0' - - -class HelpFormatter(argparse.RawDescriptionHelpFormatter): - """Set help width to screen size.""" - - def __init__(self, *args, **kwargs): - """Construct HelpFormatter using screen width.""" - if 'width' not in kwargs: - try: - size = shutil.get_terminal_size() - kwargs['width'] = size.columns - except Exception: # pylint: disable=broad-except - kwargs['width'] = 80 - - super().__init__(*args, **kwargs) - - -class PodmanArgumentParser(argparse.ArgumentParser): - """Default remote podman configuration.""" - - def __init__(self, **kwargs): - """Construct the parser.""" - kwargs['add_help'] = True - kwargs['description'] = ('Portable and simple management' - ' tool for containers and images') - kwargs['formatter_class'] = HelpFormatter - - super().__init__(**kwargs) - - def add_flag(self, *args, **kwargs): - """Add flag to parser.""" - flags = [a for a in args if a[0] in self.prefix_chars] - dest = flags[0].lstrip(self.prefix_chars) - no_flag = '{0}{0}no-{1}'.format(self.prefix_chars, dest) - - group = self.add_mutually_exclusive_group(required=False) - group.add_argument(*flags, action='store_true', dest=dest, **kwargs) - group.add_argument(no_flag, action='store_false', dest=dest, **kwargs) - default = kwargs.get('default', False) - self.set_defaults(**{dest: default}) - - def initialize_parser(self): - """Initialize parser without causing recursion meltdown.""" - self.add_argument( - '--version', - action='version', - version='%(prog)s v. ' + __version__) - self.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='WARNING', - type=str.upper, - help='set logging level for events. (default: %(default)s)', - ) - self.add_argument( - '--run-dir', - metavar='DIRECTORY', - help=('directory to place local socket bindings.' - ' (default: XDG_RUNTIME_DIR/pypodman)')) - self.add_argument( - '--username', - '-l', - help='Authenicating user on remote host. (default: {})'.format( - getpass.getuser())) - self.add_argument( - '--host', help='name of remote host. (default: None)') - self.add_argument( - '--port', - '-p', - action=PositiveIntAction, - help='port for ssh tunnel to remote host. (default: 22)') - self.add_argument( - '--remote-socket-path', - metavar='PATH', - help=('path of podman socket on remote host' - ' (default: /run/podman/io.podman)')) - self.add_argument( - '--identity-file', - '-i', - action=PathAction, - help='path to ssh identity file. (default: ~user/.ssh/id_dsa)') - self.add_argument( - '--config-home', - metavar='DIRECTORY', - action=PathAction, - help=('home of configuration "pypodman.conf".' - ' (default: XDG_CONFIG_HOME/pypodman)')) - - actions_parser = self.add_subparsers( - dest='subparser_name', help='commands') - # For create/exec/run: don't process options intended for subcommand - actions_parser.REMAINDER = argparse.REMAINDER - - # import buried here to prevent import loops - import pypodman.lib.actions # pylint: disable=cyclic-import - assert pypodman.lib.actions - - # pull in plugin(s) code for each subcommand - for name, obj in inspect.getmembers( - sys.modules['pypodman.lib.actions'], - predicate=inspect.isclass): - if hasattr(obj, 'subparser'): - try: - obj.subparser(actions_parser) - except NameError as e: - logging.critical(e) - logging.warning( - 'See subparser configuration for Class "%s"', name) - sys.exit(3) - - def parse_args(self, args=None, namespace=None): - """Parse command line arguments, backed by env var and config_file.""" - self.initialize_parser() - cooked = super().parse_args(args, namespace) - return self.resolve_configuration(cooked) - - def resolve_configuration(self, args): - """Find and fill in any arguments not passed on command line.""" - args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp') - args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME', - os.path.expanduser('~/.config')) - args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg') - - # Configuration file(s) are optional, - # required arguments may be provided elsewhere - config = {'default': {}} - dirs = args.xdg_config_dirs.split(':') - dirs.extend((args.xdg_config_home, args.config_home)) - for dir_ in dirs: - if dir_ is None: - continue - with suppress(OSError): - cnf = Path(dir_, 'pypodman', 'pypodman.conf') - with cnf.open() as stream: - config.update(pytoml.load(stream)) - - def reqattr(name, value): - """Raise an error if value is unset.""" - if value: - setattr(args, name, value) - return value - return self.error( - 'Required argument "{}" is not configured.'.format(name)) - - reqattr( - 'run_dir', - getattr(args, 'run_dir') - or os.environ.get('PODMAN_RUN_DIR') - or config['default'].get('run_dir') - or str(Path(args.xdg_runtime_dir, 'pypodman')) - ) # yapf: disable - - setattr( - args, - 'host', - getattr(args, 'host') - or os.environ.get('PODMAN_HOST') - or config['default'].get('host') - ) # yapf:disable - - reqattr( - 'username', - getattr(args, 'username') - or os.environ.get('PODMAN_USER') - or config['default'].get('username') - or os.environ.get('USER') - or os.environ.get('LOGNAME') - or getpass.getuser() - ) # yapf:disable - - reqattr( - 'port', - getattr(args, 'port') - or os.environ.get('PODMAN_PORT') - or config['default'].get('port', None) - or 22 - ) # yapf:disable - - reqattr( - 'remote_socket_path', - getattr(args, 'remote_socket_path') - or os.environ.get('PODMAN_REMOTE_SOCKET_PATH') - or config['default'].get('remote_socket_path') - or '/run/podman/io.podman' - ) # yapf:disable - - reqattr( - 'log_level', - getattr(args, 'log_level') - or os.environ.get('PODMAN_LOG_LEVEL') - or config['default'].get('log_level') - or logging.WARNING - ) # yapf:disable - - setattr( - args, - 'identity_file', - getattr(args, 'identity_file') - or os.environ.get('PODMAN_IDENTITY_FILE') - or config['default'].get('identity_file') - or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.username)) - ) # yapf:disable - - if not os.path.isfile(args.identity_file): - args.identity_file = None - - if args.host: - args.local_socket_path = str(Path(args.run_dir, 'podman.socket')) - else: - args.local_socket_path = args.remote_socket_path - - args.local_uri = 'unix:{}'.format(args.local_socket_path) - - if args.host: - components = ['ssh://', args.username, '@', args.host] - if args.port: - components.extend((':', str(args.port))) - components.append(args.remote_socket_path) - - args.remote_uri = ''.join(components) - return args - - def exit(self, status=0, message=None): - """Capture message and route to logger.""" - if message: - log = logging.info if status == 0 else logging.error - log(message) - super().exit(status) - - def error(self, message): - """Capture message and route to logger.""" - logging.error('%s: %s', self.prog, message) - logging.error("Try '%s --help' for more information.", self.prog) - super().exit(2) diff --git a/contrib/python/pypodman/pypodman/lib/report.py b/contrib/python/pypodman/pypodman/lib/report.py deleted file mode 100644 index b689390fd..000000000 --- a/contrib/python/pypodman/pypodman/lib/report.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Report Manager.""" -import string -import sys -from collections import namedtuple - - -class ReportFormatter(string.Formatter): - """Custom formatter to default missing keys to '<none>'.""" - - def get_value(self, key, args, kwargs): - """Map missing key to value '<none>'.""" - try: - if isinstance(key, int): - return args[key] - else: - return kwargs[key] - except KeyError: - return '<none>' - - -class ReportColumn(namedtuple('ReportColumn', 'key display width default')): - """Hold attributes of output column.""" - - __slots__ = () - - def __new__(cls, key, display, width, default=None): - """Add defaults for attributes.""" - return super(ReportColumn, cls).__new__(cls, key, display, width, - default) - - -class Report(): - """Report Manager.""" - - def __init__(self, columns, heading=True, epilog=None, file=sys.stdout): - """Construct Report. - - columns is a mapping for named fields to column headings. - headers True prints headers on table. - epilog will be printed when the report context is closed. - """ - self._columns = columns - self._file = file - self._format_string = None - self._formatter = ReportFormatter() - self._heading = heading - self.epilog = epilog - - def row(self, **fields): - """Print row for report.""" - if self._heading: - hdrs = {k: v.display for (k, v) in self._columns.items()} - print( - self._formatter.format(self._format_string, **hdrs), - flush=True, - file=self._file, - ) - self._heading = False - - fields = {k: str(v) for k, v in fields.items()} - print(self._formatter.format(self._format_string, **fields)) - - def __enter__(self): - """Return `self` upon entering the runtime context.""" - return self - - def __exit__(self, exc_type, exc_value, traceback): - """Leave Report context and print epilog if provided.""" - if self.epilog: - print(self.epilog, flush=True, file=self._file) - - def layout(self, iterable, keys, truncate=True): - """Use data and headings build format for table to fit.""" - fmt = [] - - for key in keys: - slice_ = [str(i.get(key, '')) for i in iterable] - data_len = len(max(slice_, key=len)) - - info = self._columns.get(key, - ReportColumn(key, key.upper(), data_len)) - display_len = max(data_len, len(info.display)) - if truncate and info.width != 0: - display_len = info.width - - fmt.append('{{{0}:{1}.{1}}}'.format(key, display_len)) - self._format_string = ' '.join(fmt) diff --git a/contrib/python/pypodman/pypodman/main.py b/contrib/python/pypodman/pypodman/main.py deleted file mode 100755 index e512dc483..000000000 --- a/contrib/python/pypodman/pypodman/main.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Remote podman client.""" -import logging -import os -import sys -from subprocess import CalledProcessError - -from pypodman.lib import PodmanArgumentParser - - -def main(): - """Entry point.""" - # Setup logging so we use stderr and can change logging level later - # Do it now before there is any chance of a default setup hardcoding crap. - log = logging.getLogger() - fmt = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s', - '%Y-%m-%d %H:%M:%S %Z') - stderr = logging.StreamHandler(stream=sys.stderr) - stderr.setFormatter(fmt) - log.addHandler(stderr) - log.setLevel(logging.WARNING) - - parser = PodmanArgumentParser() - args = parser.parse_args() - - log.setLevel(args.log_level) - logging.debug( - 'Logging initialized at level %s', - logging.getLevelName(logging.getLogger().getEffectiveLevel())) - - def want_tb(): - """Add traceback when logging events.""" - return log.getEffectiveLevel() == logging.DEBUG - - try: - if not os.path.exists(args.run_dir): - os.makedirs(args.run_dir) - except PermissionError as e: - logging.critical(e, exc_info=want_tb()) - sys.exit(6) - - # class_(args).method() are set by the sub-command's parser - returncode = None - try: - obj = args.class_(args) - except AttributeError: - parser.print_help(sys.stderr) - sys.exit(1) - except ValueError as e: - print(e, file=sys.stderr, flush=True) - sys.exit(1) - except Exception as e: # pylint: disable=broad-except - logging.critical(repr(e), exc_info=want_tb()) - logging.warning('See subparser "%s" configuration.', - args.subparser_name) - sys.exit(5) - - try: - returncode = getattr(obj, args.method)() - except KeyboardInterrupt: - pass - except AttributeError as e: - logging.critical(e, exc_info=want_tb()) - logging.warning('See subparser "%s" configuration.', - args.subparser_name) - returncode = 3 - except ( - CalledProcessError, - ConnectionError, - ConnectionRefusedError, - ConnectionResetError, - TimeoutError, - ) as e: - logging.critical(e, exc_info=want_tb()) - logging.info('Review connection arguments for correctness.') - returncode = 4 - - return 0 if returncode is None else returncode - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/contrib/python/pypodman/pypodman/test/test_report.py b/contrib/python/pypodman/pypodman/test/test_report.py deleted file mode 100644 index 280a9a954..000000000 --- a/contrib/python/pypodman/pypodman/test/test_report.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import absolute_import - -import unittest - -from report import Report, ReportColumn - - -class TestReport(unittest.TestCase): - def setUp(self): - pass - - def test_report_column(self): - rc = ReportColumn('k', 'v', 3) - self.assertEqual(rc.key, 'k') - self.assertEqual(rc.display, 'v') - self.assertEqual(rc.width, 3) - self.assertIsNone(rc.default) - - rc = ReportColumn('k', 'v', 3, 'd') - self.assertEqual(rc.key, 'k') - self.assertEqual(rc.display, 'v') - self.assertEqual(rc.width, 3) - self.assertEqual(rc.default, 'd') diff --git a/contrib/python/pypodman/requirements.txt b/contrib/python/pypodman/requirements.txt deleted file mode 100644 index ba01ed36e..000000000 --- a/contrib/python/pypodman/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -humanize -podman -pytoml -PyYAML -setuptools>=39 diff --git a/contrib/python/pypodman/setup.py b/contrib/python/pypodman/setup.py deleted file mode 100755 index f07e89201..000000000 --- a/contrib/python/pypodman/setup.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python - -import os - -from setuptools import find_packages, setup - -root = os.path.abspath(os.path.dirname(__file__)) - -with open(os.path.join(root, 'README.md')) as me: - readme = me.read() - -with open(os.path.join(root, 'requirements.txt')) as r: - requirements = r.read().splitlines() - - -setup( - name='pypodman', - version=os.environ.get('PODMAN_VERSION', '0.0.0'), - description='A client for communicating with a Podman server', - author_email='jhonce@redhat.com', - author='Jhon Honce', - license='Apache Software License', - long_description=readme, - entry_points={'console_scripts': [ - 'pypodman = pypodman.main:main', - ]}, - include_package_data=True, - install_requires=requirements, - packages=find_packages(exclude=['test']), - python_requires='>=3', - zip_safe=True, - url='http://github.com/containers/libpod', - keywords='varlink libpod podman pypodman', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 3.4', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ]) diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 20e2a84ea..16cf01976 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -185,37 +185,6 @@ Provides: bundled(golang(k8s.io/utils)) = 258e2a2fa64568210fbd6267cf1d8fd87c3cb8 %{repo} provides a library for applications looking to use the Container Pod concept popularized by Kubernetes. -%if %{with varlink} -%package -n python3-%{name} -BuildArch: noarch -BuildRequires: python3-devel -BuildRequires: python3-setuptools -BuildRequires: python3-varlink -Requires: python3-setuptools -Requires: python3-varlink -Requires: python3-dateutil -Requires: python3-humanize -Provides: python3-%{name} = %{version}-%{release} -Summary: Python 3 bindings for %{name} - -%description -n python3-%{name} -This package contains Python 3 bindings for %{name}. - -%package -n python3-py%{name} -BuildArch: noarch -BuildRequires: python3-devel -BuildRequires: python3-setuptools -BuildRequires: python3-varlink -Requires: python3-setuptools -Requires: python3-varlink -Requires: python3-dateutil -Requires: python3-psutil -Summary: Python 3 tool for %{name} - -%description -n python3-py%{name} -This package contains Python 3 tool for %{name}. -%endif # varlink - %if 0%{?with_devel} %package devel Summary: Library for applications looking to use Container Pods @@ -421,10 +390,6 @@ PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{build mv pkg/hooks/README.md pkg/hooks/README-hooks.md -%if %{with varlink} -PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} install.python -%endif # varlink - # install libpod.conf install -dp %{buildroot}%{_datadir}/containers install -p -m 644 %{repo}.conf %{buildroot}%{_datadir}/containers @@ -514,23 +479,6 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_unitdir}/io.podman.socket %{_usr}/lib/tmpfiles.d/%{name}.conf -%if %{with varlink} -%files -n python3-%{name} -%license LICENSE -%doc README.md CONTRIBUTING.md pkg/hooks/README-hooks.md install.md code-of-conduct.md transfer.md -%dir %{python3_sitelib}/%{name} -%{python3_sitelib}/%{name}/* -%{python3_sitelib}/%{name}*.egg-info - -%files -n python3-py%{name} -%license LICENSE -%doc README.md CONTRIBUTING.md pkg/hooks/README-hooks.md install.md code-of-conduct.md transfer.md -%dir %{python3_sitelib}/py%{name} -%{python3_sitelib}/py%{name}/* -%{python3_sitelib}/py%{name}*.egg-info -%{_bindir}/py%{name} -%endif # varlink - %if 0%{?with_devel} %files -n libpod-devel -f devel.file-list %license LICENSE diff --git a/docs/podman-image-sign.1.md b/docs/podman-image-sign.1.md index c4f3c6676..232bc87fe 100644 --- a/docs/podman-image-sign.1.md +++ b/docs/podman-image-sign.1.md @@ -5,8 +5,8 @@ podman-image-sign- Create a signature for an image # SYNOPSIS **podman image sign** -[**-h**|**--help**] -[**-d**, **--directory**] +[**--help**|**-h**] +[**--directory**|**-d**] [**--sign-by**] [ IMAGE... ] @@ -16,10 +16,10 @@ been pulled from a registry. The signature will be written to a directory derived from the registry configuration files in /etc/containers/registries.d. By default, the signature will be written into /var/lib/containers/sigstore directory. # OPTIONS -**-h** **--help** +**--help** **-h** Print usage statement. -**-d** **--directory** +**--directory** **-d** Store the signatures in the specified directory. Default: /var/lib/containers/sigstore **--sign-by** @@ -28,7 +28,7 @@ derived from the registry configuration files in /etc/containers/registries.d. B # EXAMPLES Sign the busybox image with the identify of foo@bar.com with a user's keyring and save the signature in /tmp/signatures/. - sudo podman image sign --sign-by foo@bar.com -d /tmp/signatures transport://privateregistry.example.com/foobar + sudo podman image sign --sign-by foo@bar.com --directory /tmp/signatures docker://privateregistry.example.com/foobar # RELATED CONFIGURATION @@ -36,7 +36,7 @@ The write (and read) location for signatures is defined in YAML-based configuration files in /etc/containers/registries.d/. When you sign an image, podman will use those configuration files to determine where to write the signature based on the the name of the originating -registry or a default storage value unless overriden with the -d +registry or a default storage value unless overriden with the --directory option. For example, consider the following configuration file. docker: diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index f1cb09bcc..50741df11 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -1,6 +1,6 @@ // +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. package libpod diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index ffc98e307..87fce7e2e 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -518,7 +518,9 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib if c.CgroupParent != "" { options = append(options, libpod.WithCgroupParent(c.CgroupParent)) } - if c.Detach { + // For a rootless container always cleanup the storage/network as they + // run in a different namespace thus not reusable when we restart. + if c.Detach || rootless.IsRootless() { options = append(options, libpod.WithExitCommand(c.createExitCommand())) } diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go index efc760364..31e41903e 100644 --- a/pkg/trust/trust.go +++ b/pkg/trust/trust.go @@ -2,6 +2,7 @@ package trust import ( "bufio" + "bytes" "encoding/base64" "encoding/json" "io/ioutil" @@ -9,7 +10,6 @@ import ( "os/exec" "path/filepath" "strings" - "unsafe" "github.com/containers/image/types" "github.com/pkg/errors" @@ -52,6 +52,14 @@ type RegistryNamespace struct { SigStoreStaging string `json:"sigstore-staging"` // For writing only. } +// ShowOutput keep the fields for image trust show command +type ShowOutput struct { + Repo string + Trusttype string + GPGid string + Sigstore string +} + // DefaultPolicyPath returns a path to the default policy of the system. func DefaultPolicyPath(sys *types.SystemContext) string { systemDefaultPolicyPath := "/etc/containers/policy.json" @@ -167,84 +175,127 @@ func CreateTmpFile(dir, pattern string, content []byte) (string, error) { return tmpfile.Name(), nil } -// GetGPGId return GPG identity, either bracketed <email> or ID string -// comma separated if more than one key -func GetGPGId(keys []string) string { +func getGPGIdFromKeyPath(path []string) []string { + var uids []string + for _, k := range path { + cmd := exec.Command("gpg2", "--with-colons", k) + results, err := cmd.Output() + if err != nil { + logrus.Warnf("error get key identity: %s", err) + continue + } + uids = append(uids, parseUids(results)...) + } + return uids +} + +func getGPGIdFromKeyData(keys []string) []string { + var uids []string for _, k := range keys { - if _, err := os.Stat(k); err != nil { - decodeKey, err := base64.StdEncoding.DecodeString(k) - if err != nil { - logrus.Warnf("error decoding key data") - continue - } - tmpfileName, err := CreateTmpFile("/run/", "", decodeKey) - if err != nil { - logrus.Warnf("error creating key date temp file %s", err) - } - defer os.Remove(tmpfileName) - k = tmpfileName + decodeKey, err := base64.StdEncoding.DecodeString(k) + if err != nil { + logrus.Warnf("error decoding key data") + continue } + tmpfileName, err := CreateTmpFile("", "", decodeKey) + if err != nil { + logrus.Warnf("error creating key date temp file %s", err) + } + defer os.Remove(tmpfileName) + k = tmpfileName cmd := exec.Command("gpg2", "--with-colons", k) results, err := cmd.Output() if err != nil { logrus.Warnf("error get key identity: %s", err) continue } - resultsStr := *(*string)(unsafe.Pointer(&results)) - scanner := bufio.NewScanner(strings.NewReader(resultsStr)) - var parseduids []string - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") { - uid := strings.Split(line, ":")[9] - if uid == "" { - continue - } - parseduid := uid - if strings.Contains(uid, "<") && strings.Contains(uid, ">") { - parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0] - } - parseduids = append(parseduids, parseduid) + uids = append(uids, parseUids(results)...) + } + return uids +} + +func parseUids(colonDelimitKeys []byte) []string { + var parseduids []string + scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") { + uid := strings.Split(line, ":")[9] + if uid == "" { + continue + } + parseduid := uid + if strings.Contains(uid, "<") && strings.Contains(uid, ">") { + parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0] } + parseduids = append(parseduids, parseduid) } - return strings.Join(parseduids, ",") } - return "" + return parseduids } -// GetPolicyJSON return the struct to show policy.json in json format -func GetPolicyJSON(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("invalid trust type %s", trustType) + } + return trustDescription +} + +// GetPolicy return the struct to show policy.json in json format and a map (reponame, ShowOutput) pair for image trust show command +func GetPolicy(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, map[string]ShowOutput, error) { registryConfigs, err := LoadAndMergeConfig(systemRegistriesDirPath) if err != nil { - return nil, err + return nil, nil, err } + trustShowOutputMap := make(map[string]ShowOutput) policyJSON := make(map[string]map[string]interface{}) if len(policyContentStruct.Default) > 0 { policyJSON["* (default)"] = make(map[string]interface{}) policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type + + var defaultPolicyStruct ShowOutput + defaultPolicyStruct.Repo = "default" + defaultPolicyStruct.Trusttype = trustTypeDescription(policyContentStruct.Default[0].Type) + trustShowOutputMap["* (default)"] = defaultPolicyStruct } for transname, transval := range policyContentStruct.Transports { for repo, repoval := range transval { + tempTrustShowOutput := ShowOutput{ + Repo: repo, + Trusttype: repoval[0].Type, + } policyJSON[repo] = make(map[string]interface{}) policyJSON[repo]["type"] = repoval[0].Type policyJSON[repo]["transport"] = transname + keyDataArr := []string{} + keyPathArr := []string{} + keyarr := []string{} for _, repoele := range repoval { - keyarr := []string{} if len(repoele.KeyPath) > 0 { keyarr = append(keyarr, repoele.KeyPath) + keyPathArr = append(keyPathArr, repoele.KeyPath) } if len(repoele.KeyData) > 0 { keyarr = append(keyarr, string(repoele.KeyData)) + keyDataArr = append(keyDataArr, string(repoele.KeyData)) } - policyJSON[repo]["keys"] = keyarr } + policyJSON[repo]["keys"] = keyarr + uids := append(getGPGIdFromKeyPath(keyPathArr), getGPGIdFromKeyData(keyDataArr)...) + tempTrustShowOutput.GPGid = strings.Join(uids, ",") + policyJSON[repo]["sigstore"] = "" registryNamespace := HaveMatchRegistry(repo, registryConfigs) if registryNamespace != nil { policyJSON[repo]["sigstore"] = registryNamespace.SigStore + tempTrustShowOutput.Sigstore = registryNamespace.SigStore } + trustShowOutputMap[repo] = tempTrustShowOutput } } - return policyJSON, nil + return policyJSON, trustShowOutputMap, nil } diff --git a/test/install/Dockerfile.Centos b/test/install/Dockerfile.Centos index 11f60029b..608d3c971 100644 --- a/test/install/Dockerfile.Centos +++ b/test/install/Dockerfile.Centos @@ -1,3 +1,3 @@ FROM registry.centos.org/centos/centos:7 -RUN yum install -y rpms/noarch/* rpms/x86_64/*
\ No newline at end of file +RUN yum install -y rpms/x86_64/* diff --git a/test/install/Dockerfile.Fedora b/test/install/Dockerfile.Fedora index 3a7b472de..74cee771d 100644 --- a/test/install/Dockerfile.Fedora +++ b/test/install/Dockerfile.Fedora @@ -1,3 +1,3 @@ FROM registry.fedoraproject.org/fedora:29 -RUN dnf install -y rpms/noarch/* rpms/x86_64/* +RUN dnf install -y rpms/x86_64/* |