{"id":13647435,"url":"https://github.com/user-cont/conu","last_synced_at":"2025-04-05T06:06:59.921Z","repository":{"id":53802469,"uuid":"103242244","full_name":"user-cont/conu","owner":"user-cont","description":"conu - python API for your containers","archived":false,"fork":false,"pushed_at":"2024-04-16T23:28:34.000Z","size":2642,"stargazers_count":166,"open_issues_count":37,"forks_count":33,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-03-29T05:06:38.199Z","etag":null,"topics":["containers","docker","fedora","testing"],"latest_commit_sha":null,"homepage":"http://conu.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/user-cont.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-12T08:14:37.000Z","updated_at":"2025-01-28T14:50:39.000Z","dependencies_parsed_at":"2022-08-22T04:01:07.398Z","dependency_job_id":"1efee368-679f-448e-ae79-4f735e85acd0","html_url":"https://github.com/user-cont/conu","commit_stats":{"total_commits":603,"total_committers":23,"mean_commits":"26.217391304347824","dds":0.6517412935323383,"last_synced_commit":"f6c3b9a07483ef5a22d3c22df38994d93b56ea0c"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/user-cont%2Fconu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/user-cont%2Fconu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/user-cont%2Fconu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/user-cont%2Fconu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/user-cont","download_url":"https://codeload.github.com/user-cont/conu/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247294536,"owners_count":20915340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["containers","docker","fedora","testing"],"created_at":"2024-08-02T01:03:33.582Z","updated_at":"2025-04-05T06:06:59.900Z","avatar_url":"https://github.com/user-cont.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# conu\n![PyPI](https://img.shields.io/pypi/v/conu.svg)\n![PyPI - License](https://img.shields.io/pypi/l/conu.svg)\n![PyPI - Status](https://img.shields.io/pypi/status/conu.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/427eb0c5dfc040cea798b23575dba025)](https://www.codacy.com/app/user-cont/conu?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=user-cont/conu\u0026amp;utm_campaign=Badge_Grade)\n[![Build Status](https://ci.centos.org/job/user-cont-conu-master/badge/icon)](https://ci.centos.org/job/user-cont-conu-master/)\n\n\n`conu` is a library which makes it easy to write tests for your containers\nand is handy when playing with containers inside your code.\nIt defines an API to access and manipulate containers,\nimages and provides more, very helpful functions.\n\n`conu` is supported on python 3.6+ only.\n\n![example](./docs/example.gif)\n\n# Installation\n\n## PyPI\n\n`conu` is available on PyPI, so you can easily install it with pip:\n\n```\n$ pip install --user conu\n```\n\n## Fedora\n\nIf you are running Fedora, we have packaged `conu` in an RPM:\n\n```\n$ dnf install python3-conu\n```\n\nPlease visit [our documentation](http://conu.readthedocs.io/en/latest/installation.html) for more info on installation.\n\n## Docker container\n\nYou can try conu also in the container, but you have to:\n- mount docker socket\n- use `--cap-add SYS_ADMIN` for mounting containers/images\n- set `--privileged` option or turn off the SELinux to allow access to docker inside the container:\n\n```\ndocker run -it --rm \\\n-v /var/run/docker.sock:/var/run/docker.sock:z \\\n--cap-add SYS_ADMIN \\\n--privileged \\\nusercont/conu:0.6.0 python3\n```\n\n```python\n\u003e\u003e\u003e from conu import DockerBackend\n\u003e\u003e\u003e backend = DockerBackend()\n11:52:13.022 backend.py        INFO   conu has initiated, welcome to the party!\n\u003e\u003e\u003e image = backend.ImageClass('docker.io/library/nginx')\n11:52:32.562 __init__.py       INFO   docker environment info: ...\n\u003e\u003e\u003e container = image.run_via_binary()\n11:52:51.910 image.py          INFO   run container via binary in background\n```\n\nIf you want to run custom source file, mount it to the container in the following way:\n\n```\ndocker run -it --rm \\\n-v /var/run/docker.sock:/var/run/docker.sock:z \\\n-v $PWD/my_source.py:/app/my_source.py:z \\\n--cap-add SYS_ADMIN \\\n--privileged \\\nusercont/conu:0.6.0 python3 /app/my_source.py\n```\n\n# Features\n\n## Container images\n- load, pull, mount and remove container images\n- obtain low-level image metadata\n- check presence of files and directories inside a container image\n- read files inside an image\n- get selinux context of files in an image\n- extend image using [s2i](https://github.com/openshift/source-to-image)\n- check all packages in image are signed with a key\n- run image inside Kubernetes pod\n\n## Container\n- kill, get logs, exec a command, mount, remove, start, stop, wait, run - via api or via binary\n- get low-level container metadata\n- shortcut methods for getting:\n    - IPv4 and IPv6 addresses\n    - PID of root process in the container\n    - port mappings\n    - container status\n- HTTP requests support\n- open a TCP connection with the service inside container\n- perform checks whether\n    - the container is running\n    - mapped ports are opened\n\n## Utilities\n- easily create and delete a directory and set its options:\n    - mode\n    - ownership\n    - selinux context\n    - access control lists (facl)\n- port availability check\n- check SELinux status on host\n- run a command on host\n- easy random string generation\n- support for probes (execute a function in a separate process):\n    - repeat until a condition is met\n    - repeat N times\n    - delay execution\n    - delay between iterations\n\n## Kubernetes\n- create/delete new namespace\n- create/delete Pod\n- create/delete Deployment\n    - with parameters\n    - from template\n- create/delete Service\n- shortcut methods for getting:\n    - pod logs\n    - pod IP\n    - pod phase\n    - pod condition\n    - service IP\n- perform checks whether\n    - pod is ready  \n    - all pods are ready for specific deployment\n\n## OpenShift\n- create new app using `oc new-app` command\n    - deploy pure image into openshift\n    - support building s2i images from remote repository\n    - support building s2i images from local path\n    - support creating new applications using OpenShift templates\n- push images to internal OpenShift registry\n- request service\n- waiting until service is ready\n- obtain logs from all pods\n- get status of application\n- check readiness of pods\n- cleanup objects of specific application in current namespace \n\n# Docker example\n\nLet's look at a practical example:\n\n```bash\n$ cat examples/readme_webserver.py\n```\n```python\n#!/usr/bin/python3\n\nimport logging\n\nfrom conu import DockerRunBuilder, DockerBackend\n\n# our webserver will be accessible on this port\nport = 8765\n\n# we'll utilize this container image\nimage_name = \"registry.fedoraproject.org/fedora\"\nimage_tag = \"27\"\n\n# we'll run our container using docker engine\nwith DockerBackend(logging_level=logging.DEBUG) as backend:\n    # the image will be pulled if it's not present\n    image = backend.ImageClass(image_name, tag=image_tag)\n\n    # the command to run in a container\n    command = [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"%d\" % port]\n    # let's run the container (in the background)\n    container = image.run_via_binary(command=command)\n    try:\n        # we need to wait for the webserver to start serving\n        container.wait_for_port(port)\n        # GET on /\n        # this is standard `requests.Response`\n        http_response = container.http_request(path=\"/\", port=port)\n        assert http_response.ok\n        assert '\u003ca href=\"etc/\"\u003eetc/\u003c/a\u003e' in http_response.content.decode(\"utf-8\")\n        # let's access /etc/passwd\n        etc_passwd = container.http_request(path=\"/etc/passwd\", port=port).content.decode(\"utf-8\")\n        assert 'root:x:0:0:root:/root:' in etc_passwd\n        # we can also access it directly on disk and compare\n        with container.mount() as fs:\n            assert etc_passwd == fs.read_file(\"/etc/passwd\")\n    finally:\n        container.kill()\n        container.delete()\n```\n\nLet's run it and look at the logs:\n```bash\n$ python3 examples/readme_webserver.py\n```\n```\n13:32:17.668 backend.py        INFO   conu has initiated, welcome to the party!\n13:32:17.668 backend.py        DEBUG  conu version: 0.1.0\n13:32:17.669 filesystem.py     INFO   initializing Directory(path=/tmp/shiny-kbjmsxgett)\n13:32:17.669 filesystem.py     DEBUG  changing permission bits of /tmp/shiny-kbjmsxgett to 0o700\n13:32:17.669 filesystem.py     INFO   initialized\n13:32:17.676 image.py          INFO   run container via binary in background\n13:32:17.676 image.py          DEBUG  docker command: ['docker', 'container', 'run', '-v', '/tmp/shiny-kbjmsxgett:/webroot', '-w', '/webroot', '-d', '--cidfile=/tmp/conu-b3jluxsc/conu-cbtbokqsedrtmiktfawbozgczdgxktmt', '-l', 'conu.test_artifact', 'sha256:9881e4229c9517b592980740ab2dfd8b5176adf7eb3be0f32b10a5dac5a3f12a', 'python3', '-m', 'http.server', '--bind', '0.0.0.0', '8765']\n13:32:17.676 __init__.py       DEBUG  command: ['docker', 'container', 'run', '-v', '/tmp/shiny-kbjmsxgett:/webroot', '-w', '/webroot', '-d', '--cidfile=/tmp/conu-b3jluxsc/conu-cbtbokqsedrtmiktfawbozgczdgxktmt', '-l', 'conu.test_artifact', 'sha256:9881e4229c9517b592980740ab2dfd8b5176adf7eb3be0f32b10a5dac5a3f12a', 'python3', '-m', 'http.server', '--bind', '0.0.0.0', '8765']\n6a0530ab32c17858180c9c3867c17a2aaf3466c6dd17c329ab7a0cf9d991f626\n13:32:18.131 probes.py         DEBUG  starting probe\n13:32:18.137 probes.py         DEBUG  Running \"\u003clambda\u003e\" with parameters: \"{}\": 0/10\n13:32:18.133 probes.py         DEBUG  first process started: pid=5812\n13:32:18.141 probes.py         DEBUG  pausing for 0.1 before next try\n13:32:18.243 probes.py         DEBUG  starting probe\n13:32:18.244 probes.py         DEBUG  first process started: pid=5828\n13:32:18.245 probes.py         DEBUG  pausing for 1 before next try\n13:32:18.246 probes.py         DEBUG  Running \"functools.partial(\u003cbound method DockerContainer.is_port_open of DockerContainer(image=registry.fedoraproject.org/fedora:27, id=6a0530ab32c17858180c9c3867c17a2aaf3466c6dd17c329ab7a0cf9d991f626)\u003e, 8765)\" with parameters: \"{}\":      0/10\n13:32:18.251 __init__.py       INFO   trying to open connection to 172.17.0.2:8765\n13:32:18.251 __init__.py       INFO   was connection successful? errno: 0\n13:32:18.251 __init__.py       DEBUG  port is opened: 172.17.0.2:8765\n13:32:19.444 filesystem.py     INFO   brace yourselves, removing '/tmp/shiny-kbjmsxgett'\n```\n\nThe test passed! The logs should be easy to read, so you should have pretty good overview of what happened.\n\n# Kubernetes\n\n## Use conu with minikube locally\n\nIf you want to test your images in Kubernetes locally, you will need to run kubernetes cluster on your host. We recommend to use minikube, for installation follow instructions in [minikube github repository](https://github.com/kubernetes/minikube).\n\nAfter that, run minikube like this:\n```bash\n$ minikube start\n```\n\n## Kubernetes example\n\n```bash\n$ cat examples/k8s_deployment.py\n```\n\n```python\nfrom conu.backend.k8s.backend import K8sBackend\nfrom conu.backend.k8s.deployment import Deployment\nfrom conu.utils import get_oc_api_token\n\n# obtain API key from OpenShift cluster. If you are not using OpenShift cluster for kubernetes tests\n# you need to replace `get_oc_api_token()` with your Bearer token. More information here:\n# https://kubernetes.io/docs/reference/access-authn-authz/authentication/\napi_key = get_oc_api_token()\n\nwith K8sBackend(api_key=api_key) as k8s_backend:\n\n    namespace = k8s_backend.create_namespace()\n\n    template = \"\"\"\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: hello-world\n      labels:\n        app: hello-world\n    spec:\n      replicas: 3\n      selector:\n        matchLabels:\n          app: hello-world\n      template:\n        metadata:\n          labels:\n            app: hello-world\n        spec:\n          containers:\n          - name: hello-openshift\n            image: openshift/hello-openshift\n    \"\"\"\n\n    test_deployment = Deployment(namespace=namespace, from_template=template,\n                                 create_in_cluster=True)\n\n    try:\n        test_deployment.wait(200)\n        assert test_deployment.all_pods_ready()\n    finally:\n        test_deployment.delete()\n        k8s_backend.delete_namespace(namespace)\n```\n\nLet's run it and look at the logs:\n\n``` bash\n$ python3 examples/k8s_deployment.py\n```\n```\n13:23:09.479 backend.py        INFO   conu has initiated, welcome to the party!\n13:23:09.523 backend.py        INFO   Creating namespace: namespace-m4cz\n13:23:14.557 backend.py        INFO   Namespace is ready!\n13:23:19.562 deployment.py     INFO   Creating Deployment hello-world in namespace: namespace-m4cz\n13:23:27.625 deployment.py     INFO   All pods are ready for deployment hello-world in namespace: namespace-m4cz\n13:23:28.620 deployment.py     INFO   Deleting Deployment hello-world in namespace: namespace-m4cz\n13:23:28.654 backend.py        INFO   Deleting namespace: namespace-m4cz\n```\n\n# Openshift\n\n## Use conu for testing locally\n\nIf you want to test your images in OpenShift locally, you need to run OpenShift cluster on your host. You can install it by following instructions in OpenShift [origin](https://github.com/openshift/origin/) or [minishift](https://github.com/minishift/minishift) github repositories.\n\nAfter that, you may need to setup cluster, here is example setup:\n``` bash\noc cluster up\noc login -u system:admin\noadm policy add-role-to-user system:registry developer\noadm policy add-role-to-user admin developer\noadm policy add-role-to-user system:image-builder developer\noadm policy add-cluster-role-to-user cluster-reader developer\noadm policy add-cluster-role-to-user admin developer\noadm policy add-cluster-role-to-user cluster-admin developer\noc login -u developer -p developer\n```\n\nFor more information, why do you need to grant all these rights to user see [accessing registry](https://docs.openshift.com/container-platform/3.3/install_config/registry/accessing_registry.html#access-user-prerequisites)\n\n## OpenShift example\n``` bash\n$ cat examples/oepnshift/openshift_s2i_remote.py\n```\n\n```python\nimport logging\n\nfrom conu.backend.origin.backend import OpenshiftBackend\nfrom conu.backend.docker.backend import DockerBackend\nfrom conu.utils import get_oc_api_token\n\napi_key = get_oc_api_token()\nwith OpenshiftBackend(api_key=api_key, logging_level=logging.DEBUG) as openshift_backend:\n    with DockerBackend(logging_level=logging.DEBUG) as backend:\n        # builder image\n        python_image = backend.ImageClass(\"centos/python-36-centos7\")\n\n        # docker login inside OpenShift internal registry\n        OpenshiftBackend.login_to_registry('developer')\n\n        # create new app from remote source in OpenShift cluster\n        app_name = openshift_backend.new_app(python_image,\n                                             source=\"https://github.com/openshift/django-ex.git\",\n                                             project='myproject')\n\n        try:\n            # wait until service is ready to accept requests\n            openshift_backend.wait_for_service(\n                app_name=app_name,\n                expected_output='Welcome to your Django application on OpenShift',\n                timeout=300)\n        finally:\n            openshift_backend.clean_project(app_name)\n\n```\n\nLet's run it and look at the logs:\n\n``` bash\n$ python3 examples/openshift/openshift_s2i_remote.py\n```\n\n```\n13:29:38.231 backend.py        INFO   conu has initiated, welcome to the party!\n13:29:38.231 backend.py        DEBUG  conu version: 0.5.0\n13:29:38.256 backend.py        INFO   conu has initiated, welcome to the party!\n13:29:38.256 backend.py        DEBUG  conu version: 0.5.0\n13:29:38.314 __init__.py       INFO   docker environment info: 'Client:\\n Version:         1.13.1\\n API version:     1.26\\n Package version: docker-1.13.1-74.git6e3bb8e.el7.centos.x86_64\\n Go version:      go1.10.3\\n Git commit:      1556cce-unsupported\\n Built:           Wed Aug  1 17:21:17 2018\\n OS/Arch:         linux/amd64\\n\\nServer:\\n Version:         1.13.1\\n API version:     1.26 (minimum version 1.12)\\n Package version: docker-1.13.1-74.git6e3bb8e.el7.centos.x86_64\\n Go version:      go1.9.4\\n Git commit:      6e3bb8e/1.13.1\\n Built:           Tue Aug 21 15:23:37 2018\\n OS/Arch:         linux/amd64\\n Experimental:    false\\n'\n13:29:38.326 backend.py        INFO   conu has initiated, welcome to the party!\n13:29:38.584 backend.py        INFO   conu has initiated, welcome to the party!\n13:29:38.656 backend.py        INFO   Login to 172.30.1.1:5000 succeed\n13:29:38.656 backend.py        INFO   conu has initiated, welcome to the party!\n13:29:38.673 image.py          INFO   The push refers to a repository [172.30.1.1:5000/myproject/python-36-centos7]\n13:29:38.689 image.py          INFO   Preparing\n13:29:38.689 image.py          INFO   Preparing\n13:29:38.689 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.690 image.py          INFO   Preparing\n13:29:38.700 image.py          INFO   Waiting\n13:29:38.701 image.py          INFO   Waiting\n13:29:38.701 image.py          INFO   Waiting\n13:29:38.701 image.py          INFO   Waiting\n13:29:38.747 image.py          INFO   Layer already exists\n13:29:38.747 image.py          INFO   Layer already exists\n13:29:38.753 image.py          INFO   Layer already exists\n13:29:38.754 image.py          INFO   Layer already exists\n13:29:38.772 image.py          INFO   Layer already exists\n13:29:38.807 image.py          INFO   Layer already exists\n13:29:38.807 image.py          INFO   Layer already exists\n13:29:38.807 image.py          INFO   Layer already exists\n13:29:38.807 image.py          INFO   Layer already exists\n13:29:39.065 image.py          INFO   latest: digest: sha256:51cf14c1d1491c5ab0e902c52740c22d4fff52f95111b97d195d12325a426350 size: 2210\n13:29:39.065 backend.py        INFO   Creating new app in project myproject\n13:29:39.558 backend.py        INFO   Waiting for service to get ready\n13:30:06.768 backend.py        INFO   Connection to service established and return expected output!\n13:30:07.729 backend.py        INFO   Deleting app\n13:30:09.504 backend.py        INFO   deploymentconfig \"app-u4ow\" deleted\n13:30:09.504 backend.py        INFO   buildconfig \"app-u4ow\" deleted\n13:30:09.504 backend.py        INFO   imagestream \"app-u4ow\" deleted\n13:30:09.504 backend.py        INFO   pod \"app-u4ow-1-vltwq\" deleted\n13:30:09.504 backend.py        INFO   service \"app-u4ow\" deleted\n\n```\n\n# Real examples\n\n- [postgresql image](https://github.com/container-images/postgresql/tree/master/test)\n- [ruby image](https://github.com/container-images/ruby/blob/master/test/test_s2i.py)\n- [memcached image](https://github.com/container-images/memcached/blob/master/tests/memcached_conu.py)\n- [php image](https://github.com/sclorg/s2i-php-container/pull/198)\n- [tools image](https://github.com/container-images/tools/pull/5)\n\n\n# Documentation\nFor more info see our documentation at [conu.readthedocs.io](http://conu.readthedocs.io/en/latest/).\n\n# How to release conu\n\nWe are using awesome [release-bot](https://github.com/user-cont/release-bot) for new conu releases.\nIf you want to make new release:\n\n- create new issue with title `x.y.z release` and wait for release bot to create new PR.\n- polish **CHANGELOG.md** and merge if tests are passing.\n- Sit down, relax and watch how release bot is doing the hard work.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuser-cont%2Fconu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuser-cont%2Fconu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuser-cont%2Fconu/lists"}