├── glanceclient ├── common │ ├── __init__.py │ ├── exceptions.py │ └── progressbar.py ├── tests │ ├── __init__.py │ ├── unit │ │ ├── __init__.py │ │ ├── v1 │ │ │ ├── __init__.py │ │ │ ├── test_versions.py │ │ │ └── test_image_members.py │ │ ├── v2 │ │ │ ├── __init__.py │ │ │ ├── test_info.py │ │ │ ├── test_versions.py │ │ │ ├── test_tags.py │ │ │ ├── test_client_requests.py │ │ │ ├── test_members.py │ │ │ ├── test_cache.py │ │ │ ├── base.py │ │ │ ├── test_metadefs_tags.py │ │ │ └── test_metadefs_resource_types.py │ │ ├── var │ │ │ ├── badcert.crt │ │ │ ├── certificate.crt │ │ │ ├── expired-cert.crt │ │ │ ├── ca.crt │ │ │ ├── wildcard-san-certificate.crt │ │ │ ├── privatekey.key │ │ │ └── wildcard-certificate.crt │ │ ├── test_base.py │ │ ├── test_exc.py │ │ ├── test_client.py │ │ └── test_progressbar.py │ └── functional │ │ ├── __init__.py │ │ ├── v1 │ │ ├── __init__.py │ │ └── test_readonly_glance.py │ │ ├── v2 │ │ ├── __init__.py │ │ ├── test_http_headers.py │ │ └── test_readonly_glance.py │ │ ├── README.rst │ │ └── base.py ├── v1 │ ├── apiclient │ │ ├── __init__.py │ │ └── utils.py │ ├── __init__.py │ ├── versions.py │ ├── client.py │ └── image_members.py ├── v2 │ ├── __init__.py │ ├── info.py │ ├── versions.py │ ├── image_tags.py │ ├── cache.py │ ├── image_members.py │ ├── resource_type_schema.py │ ├── client.py │ ├── schemas.py │ └── tasks.py ├── _i18n.py ├── __init__.py ├── client.py └── exc.py ├── releasenotes ├── notes │ ├── .placeholder │ ├── del_from_store-2d807c3038283907.yaml │ ├── image-tasks-api-ee3ea043557a1dfa.yaml │ ├── remove-py38-e14e0516991682eb.yaml │ ├── remove-py39-63731125072287a3.yaml │ ├── copy-existing-image-619b7e6bc3394446.yaml │ ├── add-member-get-command-11c15e0a94ecd39a.yaml │ ├── drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml │ ├── multi-store-import-45d05a6193ef2c04.yaml │ ├── sess_client_grid-3c2101609110f413.yaml │ ├── fix_1889666-22dc97ce577eccc6.yaml │ ├── headers-encoding-bug-rocky-889ccd885a9cc4e8.yaml │ ├── add-support-for-glance-download-import-method-10525254db3e8e7a.yaml │ ├── log-request-id-e7f67a23a0ed5c7b.yaml │ ├── drop-py-2-7-f10417b8d1dd38fb.yaml │ ├── fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml │ ├── 4.3.0_Release-1a7acbd472e16c72.yaml │ ├── 4.6.0_releasenotes-99ed8ea49481ee01.yaml │ ├── rm-deprecate-ssl-opts-c88225a4ba2285ad.yaml │ ├── 4.4.0_Release-a3c89184f345e5a2.yaml │ ├── return-request-id-to-caller-47f4c0a684b1d88e.yaml │ ├── multihash-filter-ef2a48dc48fae9dc.yaml │ ├── 2.16.0_Release-43ebe06b74a272ba.yaml │ ├── rocky-2.11.0-ba936fd5e969198d.yaml │ ├── boolean-properties-strict-checking-bdd624b5da81e723.yaml │ ├── validation-data-support-dfb2463914818cd2.yaml │ ├── bp-use-keystoneauth-e12f300e58577b13.yaml │ ├── http-headers-per-rfc-8187-aafa3199f863be81.yaml │ ├── check-for-md5-59db8fd67870b214.yaml │ ├── multihash-support-f1474590cf3ef5cf.yaml │ ├── 3.1.0_Release-1337ddc753b88905.yaml │ ├── hidden-images-support-9e2277ad62bf0d31.yaml │ ├── add_new_locations_apis_support-1ceb47178d384d58.yaml │ ├── 2.17.0_Release-c67392be3b428d10.yaml │ ├── multi-store-support-acc7ad0e7e8b6f99.yaml │ ├── 3.6.0_Release-04d3b5017747290b.yaml │ ├── multihash-download-verification-596e91bf7b68e7db.yaml │ └── pike-relnote-2c77b01aa8799f35.yaml └── source │ ├── _static │ └── .placeholder │ ├── _templates │ └── .placeholder │ ├── unreleased.rst │ ├── xena.rst │ ├── yoga.rst │ ├── zed.rst │ ├── 2023.2.rst │ ├── 2024.2.rst │ ├── 2025.1.rst │ ├── 2025.2.rst │ ├── ussuri.rst │ ├── wallaby.rst │ ├── 2023.1.rst │ ├── 2024.1.rst │ ├── victoria.rst │ ├── pike.rst │ ├── rocky.rst │ ├── stein.rst │ ├── train.rst │ ├── queens.rst │ ├── mitaka.rst │ ├── newton.rst │ ├── ocata.rst │ └── index.rst ├── .stestr.conf ├── .gitreview ├── .coveragerc ├── .mailmap ├── tools ├── with_venv.sh ├── glance.bash_completion └── fix_ca_bundle.sh ├── HACKING.rst ├── requirements.txt ├── test-requirements.txt ├── doc ├── requirements.txt └── source │ ├── index.rst │ ├── reference │ ├── index.rst │ └── apiv2.rst │ ├── cli │ ├── index.rst │ ├── property-keys.rst │ └── glance.rst │ └── conf.py ├── .gitignore ├── CONTRIBUTING.rst ├── setup.py ├── setup.cfg ├── run_tests.sh ├── README.rst ├── tox.ini └── .zuul.yaml /glanceclient/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /releasenotes/notes/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/v1/apiclient/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/badcert.crt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /releasenotes/source/_static/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /releasenotes/source/_templates/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=${OS_TEST_PATH:-./glanceclient/tests/unit} 3 | top_path=./ 4 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/python-glanceclient.git 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = glanceclient 4 | omit = glanceclient/tests/* 5 | 6 | [report] 7 | ignore_errors = True 8 | -------------------------------------------------------------------------------- /releasenotes/notes/del_from_store-2d807c3038283907.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Support for deleting the image data from single store. 5 | -------------------------------------------------------------------------------- /releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Support for showing tasks associated with given image. 5 | 6 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Current Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /releasenotes/source/xena.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Xena Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: xena-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/yoga.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Yoga Series Release Notes 3 | ========================= 4 | 5 | .. release-notes:: 6 | :branch: yoga-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/zed.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Zed Series Release Notes 3 | ======================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/zed 7 | -------------------------------------------------------------------------------- /releasenotes/source/2023.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2023.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2024.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/2025.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2025.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2025.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2025.2.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2025.2 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/2025.2 7 | -------------------------------------------------------------------------------- /releasenotes/source/ussuri.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Ussuri Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: stable/ussuri 7 | -------------------------------------------------------------------------------- /releasenotes/source/wallaby.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Wallaby Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | :branch: wallaby-eom 7 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-py38-e14e0516991682eb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 3.8 has been removed. Now the minimum python version 5 | supported is 3.9 . 6 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-py39-63731125072287a3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Pyton 3.9 has been removed. Now Python 3.10 is the minimum 5 | version supported. 6 | -------------------------------------------------------------------------------- /releasenotes/source/2023.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2023.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/2023.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/2024.1.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 2024.1 Series Release Notes 3 | =========================== 4 | 5 | .. release-notes:: 6 | :branch: unmaintained/2024.1 7 | -------------------------------------------------------------------------------- /releasenotes/source/victoria.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Victoria Series Release Notes 3 | ============================= 4 | 5 | .. release-notes:: 6 | :branch: victoria-eom 7 | -------------------------------------------------------------------------------- /releasenotes/source/pike.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Pike Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/pike 7 | -------------------------------------------------------------------------------- /releasenotes/source/rocky.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Rocky Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/rocky 7 | -------------------------------------------------------------------------------- /releasenotes/source/stein.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Stein Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/stein 7 | -------------------------------------------------------------------------------- /releasenotes/source/train.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Train Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/train 7 | -------------------------------------------------------------------------------- /releasenotes/source/queens.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Queens Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: stable/queens 7 | -------------------------------------------------------------------------------- /releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for copy-image import method which will copy existing 5 | images into multiple stores. 6 | -------------------------------------------------------------------------------- /releasenotes/source/mitaka.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Mitaka Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/mitaka 7 | -------------------------------------------------------------------------------- /releasenotes/source/newton.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Newton Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/newton 7 | -------------------------------------------------------------------------------- /releasenotes/source/ocata.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Ocata Series Release Notes 3 | =================================== 4 | 5 | .. release-notes:: 6 | :branch: origin/stable/ocata 7 | -------------------------------------------------------------------------------- /releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 1938154_: Added member-get command 5 | 6 | .. _1938154: https://bugs.launchpad.net/glance/+bug/1938154 7 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now 5 | supported is Python 3.8. 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # "man git-shortlog" for reference 2 | 3 | 4 | David Koo 5 | -------------------------------------------------------------------------------- /releasenotes/notes/multi-store-import-45d05a6193ef2c04.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for multi-store import where user can import 5 | image into multiple backend stores with single command. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/sess_client_grid-3c2101609110f413.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | * Bug 1886650_: Glance client does not correctly forward global request IDs 5 | 6 | .. _1886650: https://code.launchpad.net/bugs/1886650 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix_1889666-22dc97ce577eccc6.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 1889666_: 'stores' property added when using image-create-via-import --stores 5 | 6 | .. _1889666: https://bugs.launchpad.net/glance/+bug/1889666 7 | -------------------------------------------------------------------------------- /releasenotes/notes/headers-encoding-bug-rocky-889ccd885a9cc4e8.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | * Bug 1783290_: glance will return 401 error if the request token contains url code 5 | 6 | .. _1783290: https://code.launchpad.net/bugs/1783290 7 | -------------------------------------------------------------------------------- /releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add support for new ``glance-download`` image-import method to 5 | import image from another glance/region in federated deployment. 6 | -------------------------------------------------------------------------------- /tools/with_venv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | command -v tox > /dev/null 2>&1 4 | if [ $? -ne 0 ]; then 5 | echo 'This script requires "tox" to run.' 6 | echo 'You can install it with "pip install tox".' 7 | exit 1; 8 | fi 9 | 10 | tox -evenv -- $@ 11 | -------------------------------------------------------------------------------- /releasenotes/notes/log-request-id-e7f67a23a0ed5c7b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Added support to log 'x-openstack-request-id' for each api call. 4 | Please refer, 5 | https://blueprints.launchpad.net/python-glanceclient/+spec/log-request-id 6 | for more details. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-py-2-7-f10417b8d1dd38fb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Python 2.7 support has been dropped. Last release of python-glanceclient 5 | to support py2.7 is OpenStack Train. The minimum version of Python now 6 | supported by python-glanceclient is Python 3.6. 7 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | Glance Style Commandments 2 | ========================= 3 | 4 | - Step 1: Read the OpenStack Style Commandments 5 | https://docs.openstack.org/hacking/latest/ 6 | - Step 2: Read on 7 | 8 | 9 | Glance Specific Commandments 10 | ---------------------------- 11 | 12 | None so far 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pbr!=2.1.0,>=2.0.0 # Apache-2.0 2 | PrettyTable>=0.7.1 # BSD 3 | keystoneauth1>=3.6.2 # Apache-2.0 4 | requests>=2.14.2 # Apache-2.0 5 | warlock>=1.2.0 # Apache-2.0 6 | oslo.utils>=3.33.0 # Apache-2.0 7 | oslo.i18n>=3.15.3 # Apache-2.0 8 | wrapt>=1.7.0 # BSD License 9 | pyOpenSSL>=17.1.0 # Apache-2.0 10 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | `Bug #1903727 `_: 5 | Fixed raw Python error message when using ``glance`` without 6 | a subcommand while passing an optional argument, such as 7 | ``--os-image-api-version``. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 911805_: Scriptify the generation of man pages 5 | - | 6 | Bug 1561828_: unallowed and read-only parameters in namspace and resouce_type 7 | 8 | .. _911805: https://code.launchpad.net/bugs/911805 9 | .. _1561828: https://code.launchpad.net/bugs/1561828 10 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | hacking>=6.1.0,<6.2.0 # Apache-2.0 2 | coverage!=4.4,>=4.0 # Apache-2.0 3 | openstacksdk>=0.10.0 # Apache-2.0 4 | stestr>=2.0.0 # Apache-2.0 5 | testtools>=2.2.0 # MIT 6 | testscenarios>=0.4 # Apache-2.0/BSD 7 | ddt>=1.2.1 # MIT 8 | fixtures>=3.0.0 # Apache-2.0/BSD 9 | requests-mock>=1.2.0 # Apache-2.0 10 | tempest>=17.1.0 # Apache-2.0 11 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | openstackdocstheme>=2.2.1 # Apache-2.0 5 | reno>=3.1.0 # Apache-2.0 6 | sphinx>=2.0.0,!=2.1.0 # BSD 7 | sphinxcontrib-apidoc>=0.2.0 # BSD 8 | -------------------------------------------------------------------------------- /releasenotes/notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 2051712_: glanceclient leaks X-Auth-Token into debug log 5 | - | 6 | Bug 2064011_: Some unit test cases are broken with requests-mock >= 1.12.0 7 | 8 | .. _2051712: https://code.launchpad.net/bugs/2051712 9 | .. _2064011: https://code.launchpad.net/bugs/2064011 10 | -------------------------------------------------------------------------------- /releasenotes/notes/rm-deprecate-ssl-opts-c88225a4ba2285ad.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - | 4 | The following options to the command line client, which have been 5 | deprecated since Icehouse, have been removed: 6 | 7 | * ``--key-file`` (use ``--os-key`` instead) 8 | * ``--ca-file`` (use ``--os-cacert`` instead) 9 | * ``--cert-file`` (use ``--os-cert`` instead) 10 | -------------------------------------------------------------------------------- /releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 2012442_: import image with glance-download return 400 5 | - | 6 | Bug 1934626_: glanceclient has no support to add type while creating md-property for namespace 7 | 8 | .. _2012442: https://code.launchpad.net/bugs/2012442 9 | .. _1934626: https://code.launchpad.net/bugs/1934626 10 | -------------------------------------------------------------------------------- /releasenotes/notes/return-request-id-to-caller-47f4c0a684b1d88e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Added support to return "x-openstack-request-id" header in request_ids attribute 4 | for better tracing. 5 | 6 | | For ex. 7 | | >>> from glanceclient import Client 8 | | >>> glance = Client('2', endpoint='OS_IMAGE_ENDPOINT', token='OS_AUTH_TOKEN') 9 | | >>> res = glance.images.get('') 10 | | >>> res.request_ids 11 | -------------------------------------------------------------------------------- /releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | For parity with the old ``checksum`` field, this release adds the 5 | ability for CLI users to filter the image list based upon a particular 6 | multihash value using the ``--hash `` option. Issue the 7 | command: 8 | 9 | .. code-block:: none 10 | 11 | glance help image-list 12 | 13 | for more information. 14 | -------------------------------------------------------------------------------- /releasenotes/notes/2.16.0_Release-43ebe06b74a272ba.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | This version of python-glanceclient adds Python 3.6 classifier and gating 4 | on Python 3.7 environment. 5 | fixes: 6 | - | 7 | * Bug 1788271_: Add image-list filter for multihash 8 | * Bug 1598714_: Remove redundant information from error message 9 | 10 | .. _1788271: https://code.launchpad.net/bugs/1788271 11 | .. _1598714: https://code.launchpad.net/bugs/1598714 12 | 13 | -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | glanceclient Release Notes 3 | ========================== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | 2025.2 10 | 2025.1 11 | 2024.2 12 | 2024.1 13 | 2023.2 14 | 2023.1 15 | zed 16 | yoga 17 | xena 18 | wallaby 19 | victoria 20 | ussuri 21 | train 22 | stein 23 | rocky 24 | queens 25 | pike 26 | ocata 27 | newton 28 | mitaka 29 | earlier 30 | -------------------------------------------------------------------------------- /releasenotes/notes/rocky-2.11.0-ba936fd5e969198d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | issues: 3 | - | 4 | Help texts for some properties has possibly outdated links. Please refer 5 | to the documentation of the deployment while we try to find a way how to 6 | document these references in a way that they do not point user to false 7 | information. 8 | fixes: 9 | - | 10 | * Bug 1762044_: Sync schema with glance-api service 11 | 12 | .. _1762044: https://code.launchpad.net/bugs/1762044 13 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | Python Bindings for the OpenStack Images API 3 | ============================================== 4 | 5 | This is a client for the OpenStack Images API. There's :doc:`a Python 6 | API ` (the :mod:`glanceclient` module) and a 7 | :doc:`command-line script ` (installed as 8 | :program:`glance`). 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | reference/index 14 | cli/index 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | subunit.log 3 | .venv 4 | *,cover 5 | cover 6 | *.pyc 7 | .idea 8 | *.sw? 9 | *~ 10 | AUTHORS 11 | build 12 | dist 13 | python_glanceclient.egg-info 14 | ChangeLog 15 | run_tests.err.log 16 | .testrepository 17 | .stestr/ 18 | .tox 19 | doc/source/api 20 | doc/build 21 | *.egg 22 | .eggs/* 23 | glanceclient/versioninfo 24 | # Files created by releasenotes build 25 | releasenotes/build 26 | # File created by docs build process 27 | /doc/source/ref 28 | /doc/source/reference/api/* 29 | -------------------------------------------------------------------------------- /releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | fixes: 4 | - | 5 | * Bug 1607317_: metadata def namespace update CLI is not working as expected for parameter "protected" 6 | 7 | .. _1607317: https://code.launchpad.net/bugs/1607317 8 | other: 9 | - | 10 | Boolean arguments now expect one of the following values: '0', '1', 'f', 11 | 'false', 'n', 'no', 'off', 'on', 't', 'true', 'y', 'yes' 12 | (case-insensitive). This will not change anything for most users. 13 | -------------------------------------------------------------------------------- /releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Support for embedding validation data (checksum and multihash) when adding 5 | a location to an image. Requires the Stein release server-side. 6 | 7 | The ``glance.images.add_location()`` method now accepts an optional 8 | argument ``validation_data``, in the form of a dictionary containing 9 | ``checksum``, ``os_hash_algo`` and ``os_hash_value``. 10 | 11 | The ``location-add`` command now accepts optional arguments ``--checksum``, 12 | ``--hash-algo`` and ``--hash-value``. 13 | -------------------------------------------------------------------------------- /releasenotes/notes/bp-use-keystoneauth-e12f300e58577b13.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | Switch to using keystoneauth for session and auth plugins. 4 | other: 5 | - > 6 | [`bp use-keystoneauth `_] 7 | As of keystoneclient 2.2.0, the session and auth plugins code has 8 | been deprecated. These modules have been moved to the keystoneauth 9 | library. Consumers of the session and plugin modules are encouraged 10 | to move to keystoneauth. Note that there should be no change to 11 | end users of glanceclient. 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of OpenStack, 2 | you must follow the steps documented at: 3 | 4 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 5 | 6 | Once those steps have been completed, changes to OpenStack 7 | should be submitted for review via the Gerrit tool, following 8 | the workflow documented at: 9 | 10 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 11 | 12 | Pull requests submitted through GitHub will be ignored. 13 | 14 | Bugs should be filed on Launchpad, not GitHub: 15 | 16 | https://bugs.launchpad.net/python-glanceclient 17 | -------------------------------------------------------------------------------- /releasenotes/notes/http-headers-per-rfc-8187-aafa3199f863be81.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Bug 1766235_: Handle HTTP headers per RFC 8187 5 | 6 | Previously the glanceclient encoded HTTP headers as UTF-8 7 | bytes. According to `RFC 8187`_, however, headers should be 8 | encoded as 7-bit ASCII. The glanceclient now sends all headers 9 | as 7-bit ASCII. It handles unicode strings by percent-encoding_ 10 | them before sending them in headers. 11 | 12 | .. _1766235: https://code.launchpad.net/bugs/1766235 13 | .. _RFC 8187: https://tools.ietf.org/html/rfc8187 14 | .. _percent-encoding: https://tools.ietf.org/html/rfc3986#section-2.1 15 | -------------------------------------------------------------------------------- /glanceclient/v2/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Mirantis, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | from glanceclient.v2.client import Client # noqa 16 | -------------------------------------------------------------------------------- /glanceclient/v1/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from glanceclient.v1.client import Client # noqa 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | setuptools.setup( 19 | setup_requires=['pbr>=2.0.0'], 20 | pbr=True) 21 | -------------------------------------------------------------------------------- /releasenotes/notes/check-for-md5-59db8fd67870b214.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | -| 4 | For legacy (pre-Rocky) images that do not contain "multihash" metadata, 5 | or when the ``--allow-md5-fallback`` option is used in cases where the 6 | multihash metadata is present but the specified algorithm is not available 7 | to the glanceclient, the glanceclient uses an MD5 checksum to validate 8 | the download. When operating in a FIPS-compliant environment, however, 9 | the MD5 algorithm may be unavailable to the glanceclient. In such a case, 10 | (that is, when the MD5 checksum information is available to the glanceclient 11 | but the MD5 algorithm is not), the glanceclient will fail the download as 12 | corrupt because it cannot prove otherwise. This is consistent with 13 | current behavior. 14 | -------------------------------------------------------------------------------- /glanceclient/common/exceptions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | # This is here for compatibility purposes. Once all known OpenStack clients 14 | # are updated to use glanceclient.exc, this file should be removed 15 | from glanceclient.exc import * # noqa 16 | -------------------------------------------------------------------------------- /releasenotes/notes/multihash-support-f1474590cf3ef5cf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | This release adds client support for the Glance "multihash" feature 5 | introduced in Rocky. This feature introduces two new image properties, 6 | ``os_hash_algo`` and ``os_hash_value``. The content of ``os_hash_algo`` 7 | is an algorithm identifier recognized by the Python ``hashlib`` library. 8 | The ``os_hash_value`` is a hexdigest of the image data computed using 9 | this algorithm. The ``os_hash_algo`` is not end-user settable; it 10 | is configured in Glance by the cloud operator. In the glanceclient, 11 | the feature is limited solely to the display of these values. 12 | 13 | If the "multihash" properties are not available on an image, their 14 | values are displayed as ``None`` in the glanceclient image-show and 15 | image-list responses. 16 | -------------------------------------------------------------------------------- /glanceclient/_i18n.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | import oslo_i18n as i18n 16 | 17 | 18 | _translators = i18n.TranslatorFactory(domain='glanceclient') 19 | 20 | # The primary translation function using the well-known name "_" 21 | _ = _translators.primary 22 | -------------------------------------------------------------------------------- /glanceclient/v2/info.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | class Controller: 17 | def __init__(self, http_client, schema_client): 18 | self.http_client = http_client 19 | self.schema_client = schema_client 20 | 21 | def get_usage(self, **kwargs): 22 | resp, body = self.http_client.get('/v2/info/usage') 23 | return body['usage'] 24 | -------------------------------------------------------------------------------- /doc/source/reference/index.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Python Library Reference 3 | ========================== 4 | 5 | In order to use the python api directly, you must first obtain an auth 6 | token and identify which endpoint you wish to speak to. Once you have 7 | done so, you can use the API like so:: 8 | 9 | >>> from glanceclient import Client 10 | >>> glance = Client('1', endpoint=OS_IMAGE_ENDPOINT, token=OS_AUTH_TOKEN) 11 | >>> image = glance.images.create(name="My Test Image") 12 | >>> print image.status 13 | 'queued' 14 | >>> image.update(data=open('/tmp/myimage.iso', 'rb')) 15 | >>> print image.status 16 | 'active' 17 | >>> image.update(properties=dict(my_custom_property='value')) 18 | >>> with open('/tmp/copyimage.iso', 'wb') as f: 19 | for chunk in image.data(): 20 | f.write(chunk) 21 | >>> image.delete() 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | 26 | Python API Reference 27 | apiv2 28 | -------------------------------------------------------------------------------- /tools/glance.bash_completion: -------------------------------------------------------------------------------- 1 | _glance_opts="" # lazy init 2 | _glance_flags="" # lazy init 3 | _glance_opts_exp="" # lazy init 4 | _glance() 5 | { 6 | local cur prev nbc cflags 7 | COMPREPLY=() 8 | cur="${COMP_WORDS[COMP_CWORD]}" 9 | prev="${COMP_WORDS[COMP_CWORD-1]}" 10 | 11 | if [ "x$_glance_opts" == "x" ] ; then 12 | nbc="`glance bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" 13 | _glance_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" 14 | _glance_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" 15 | _glance_opts_exp="`echo "$_glance_opts" | sed 's/^ *//' | tr ' ' '|'`" 16 | fi 17 | 18 | if [[ " ${COMP_WORDS[@]} " =~ " "($_glance_opts_exp)" " && "$prev" != "help" ]] ; then 19 | COMPREPLY=($(compgen -W "${_glance_flags}" -- ${cur})) 20 | else 21 | COMPREPLY=($(compgen -W "${_glance_opts}" -- ${cur})) 22 | fi 23 | return 0 24 | } 25 | complete -F _glance glance -------------------------------------------------------------------------------- /glanceclient/v1/versions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # Copyright 2015 Huawei Corp. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from glanceclient.v1.apiclient import base 18 | 19 | 20 | class VersionManager(base.ManagerWithFind): 21 | 22 | def list(self): 23 | """List all versions.""" 24 | url = '/versions' 25 | resp, body = self.client.get(url) 26 | return body.get('versions', None) 27 | -------------------------------------------------------------------------------- /releasenotes/notes/3.1.0_Release-1337ddc753b88905.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: | 3 | This version of python-glanceclient finalizes client-side support for 4 | the Glance import image in multiple stores, copy existing image in 5 | multiple stores and delete image from single store. 6 | fixes: 7 | - | 8 | Bug 1838694: glanceclient doesn't cleanup session it creates if one is not provided 9 | 10 | .. _1838694: https://bugs.launchpad.net/python-glanceclient/+bug/1838694 11 | upgrade: 12 | - | 13 | The following Command Line Interface calls now take ``--stores``, 14 | ``--all-stores`` and ``--allow-failure`` option: 15 | 16 | * ``glance image-create-via-import`` 17 | * ``glance image-import`` 18 | 19 | The value for ``--stores`` option is a list of store identifiers. The 20 | list of available stores may be obtained from the ``glance stores-info`` 21 | command. 22 | 23 | The value for ``--all-stores`` option could be True or False. 24 | 25 | The value for ``--allow-failure`` option could be True or False. 26 | -------------------------------------------------------------------------------- /glanceclient/v2/versions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # Copyright 2015 Huawei Corp. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | 18 | class VersionController(object): 19 | def __init__(self, http_client): 20 | self.http_client = http_client 21 | 22 | def list(self): 23 | """List all versions.""" 24 | url = '/versions' 25 | resp, body = self.http_client.get(url) 26 | return body.get('versions', None) 27 | -------------------------------------------------------------------------------- /releasenotes/notes/hidden-images-support-9e2277ad62bf0d31.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | This release adds client support for the Glance "hidden images" 5 | feature described in the spec `Operator maintained images lifecycle 6 | `_. 7 | 8 | Support in the glanceclient includes the following: 9 | 10 | - The following calls now allow the specification of a ``--hidden`` 11 | option that takes a boolean value (``true`` or ``false``). When 12 | this option is omitted, the default value is ``false``. 13 | 14 | * ``image-create`` 15 | * ``image-create-via-import`` 16 | * ``image-update`` 17 | 18 | - The ``image-list`` call now allows the specification of a 19 | ``--hidden`` filter that takes a boolean value (``true`` or 20 | ``false``). By default, "hidden" images are not displayed 21 | in the ``image-list`` response (that's why they're called 22 | "hidden"). To see those images, use ``--hidden true`` as a 23 | filter on the ``image-list`` call. 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-glanceclient 3 | summary = OpenStack Image API Client Library 4 | description_file = 5 | README.rst 6 | license = Apache License, Version 2.0 7 | author = OpenStack 8 | author_email = openstack-discuss@lists.openstack.org 9 | home_page = https://docs.openstack.org/python-glanceclient/latest/ 10 | python_requires = >=3.10 11 | classifier = 12 | Development Status :: 5 - Production/Stable 13 | Environment :: Console 14 | Environment :: OpenStack 15 | Intended Audience :: Information Technology 16 | Intended Audience :: System Administrators 17 | License :: OSI Approved :: Apache Software License 18 | Operating System :: POSIX :: Linux 19 | Programming Language :: Python 20 | Programming Language :: Python :: Implementation :: CPython 21 | Programming Language :: Python :: 3 :: Only 22 | Programming Language :: Python :: 3 23 | Programming Language :: Python :: 3.10 24 | Programming Language :: Python :: 3.11 25 | Programming Language :: Python :: 3.12 26 | 27 | [files] 28 | packages = 29 | glanceclient 30 | 31 | [entry_points] 32 | console_scripts = 33 | glance = glanceclient.shell:main 34 | -------------------------------------------------------------------------------- /doc/source/cli/index.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Command-line Tool Reference 3 | ============================= 4 | 5 | In order to use the CLI, you must provide your OpenStack username, 6 | password, tenant, and auth endpoint. Use the corresponding 7 | configuration options (``--os-username``, ``--os-password``, 8 | ``--os-project-id``, and ``--os-auth-url``) or set them in environment 9 | variables:: 10 | 11 | export OS_USERNAME=user 12 | export OS_PASSWORD=pass 13 | export OS_PROJECT_ID=b363706f891f48019483f8bd6503c54b 14 | export OS_AUTH_URL=http://auth.example.com:5000/v2.0 15 | 16 | The command line tool will attempt to reauthenticate using your 17 | provided credentials for every request. You can override this behavior 18 | by manually supplying an auth token using ``--os-image-url`` and 19 | ``--os-auth-token``. You can alternatively set these environment 20 | variables:: 21 | 22 | export OS_IMAGE_URL=http://glance.example.org:9292/ 23 | export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 24 | 25 | Once you've configured your authentication parameters, you can run 26 | ``glance help`` to see a complete listing of available commands. 27 | 28 | .. toctree:: 29 | 30 | details 31 | property-keys 32 | glance 33 | -------------------------------------------------------------------------------- /glanceclient/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | # NOTE(bcwaldon): this try/except block is needed to run setup.py due to 16 | # its need to import local code before installing required dependencies 17 | try: 18 | import glanceclient.client 19 | Client = glanceclient.client.Client 20 | except ImportError: 21 | import warnings 22 | warnings.warn("Could not import glanceclient.client", ImportWarning) 23 | 24 | import pbr.version 25 | 26 | version_info = pbr.version.VersionInfo('python-glanceclient') 27 | 28 | try: 29 | __version__ = version_info.version_string() 30 | except AttributeError: 31 | __version__ = None 32 | -------------------------------------------------------------------------------- /releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add support for the new Glance ``Locations`` APIs 5 | 6 | - Add client support for newly added API, 7 | ``POST /v2/images/{image_id}/locations`` in Glance. 8 | New add location operation is allowed for service to service 9 | interaction, end users only when `http` store is enabled in 10 | deployment and images which are in ``queued`` state only. 11 | This api replaces the image-update (old location-add) mechanism 12 | for consumers like cinder and nova to address `OSSN-0090`_ and 13 | `OSSN-0065`_. This client change adds support of new shell command 14 | ``add-location`` and new client method ``add_image_location``. 15 | - Add support for newly added API, 16 | ``GET /v2/images/{image_id}/locations`` in Glance to fetch the 17 | locations associated to an image. This change adds new client method 18 | ``get_image_locations`` since this new get locations api is meant for 19 | service user only hence it is not exposed to the end user as a shell 20 | command. 21 | 22 | .. _OSSN-0090: https://wiki.openstack.org/wiki/OSSN/OSSN-0090 23 | .. _OSSN-0065: https://wiki.openstack.org/wiki/OSSN/OSSN-0065 24 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function usage { 4 | echo "Usage: $0 [OPTION]..." 5 | echo "Run python-glanceclient's test suite(s)" 6 | echo "" 7 | echo " -p, --pep8 Just run flake8" 8 | echo " -h, --help Print this usage message" 9 | echo "" 10 | echo "This script is deprecated and currently retained for compatibility." 11 | echo 'You can run the full test suite for multiple environments by running "tox".' 12 | echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' 13 | echo 'the flake8 tests with "tox -e pep8".' 14 | exit 15 | } 16 | 17 | command -v tox > /dev/null 2>&1 18 | if [ $? -ne 0 ]; then 19 | echo 'This script requires "tox" to run.' 20 | echo 'You can install it with "pip install tox".' 21 | exit 1; 22 | fi 23 | 24 | just_pep8=0 25 | 26 | function process_option { 27 | case "$1" in 28 | -h|--help) usage;; 29 | -p|--pep8) let just_pep8=1;; 30 | esac 31 | } 32 | 33 | for arg in "$@"; do 34 | process_option $arg 35 | done 36 | 37 | if [ $just_pep8 -eq 1 ]; then 38 | tox -e pep8 39 | exit 40 | fi 41 | 42 | tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit 43 | if [ ${PIPESTATUS[0]} -ne 0 ]; then 44 | exit ${PIPESTATUS[0]} 45 | fi 46 | 47 | if [ -z "$toxargs" ]; then 48 | tox -e pep8 49 | fi 50 | -------------------------------------------------------------------------------- /doc/source/cli/property-keys.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Image service property keys 3 | =========================== 4 | 5 | You can use the glanceclient command line interface to set image properties 6 | that can be consumed by other services to affect the behavior of those other 7 | services. 8 | 9 | Properties can be set on an image at the time of image creation or they 10 | can be set on an existing image. Use the :command:`glance image-create` 11 | and :command:`glance image-update` commands respectively. 12 | 13 | For example: 14 | 15 | .. code-block:: console 16 | 17 | $ glance image-update IMG-UUID --property architecture=x86_64 18 | 19 | For a list of image properties that can be used to affect the behavior 20 | of other services, refer to `Useful image properties 21 | `_ 22 | in the Glance Administration Guide. 23 | 24 | .. note:: 25 | 26 | Behavior set using image properties overrides behavior set using flavors. 27 | For more information, refer to `Manage images 28 | `_ 29 | in the Glance Administration Guide. 30 | 31 | .. note:: 32 | 33 | Boolean properties expect one of the following values: '0', '1', 'f', 34 | 'false', 'n', 'no', 'off', 'on', 't', 'true', 'y', 'yes' (case-insensitive). 35 | -------------------------------------------------------------------------------- /releasenotes/notes/2.17.0_Release-c67392be3b428d10.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: | 3 | This version of python-glanceclient finalizes client-side support for 4 | the Glance multiple stores feature. See the `Multi Store Support 5 | `_ 6 | section of the Glance documentation for more information. 7 | 8 | Support for Glance multiple stores has been available on an EXPERIMENTAL 9 | basis since release 2.12.0. For the Train release, the Image service 10 | has finalized how API users interact with multiple stores. See the 11 | "Upgrade Notes" section of this document for information about changes this 12 | has necessitated in multistore support in the glanceclient. 13 | fixes: 14 | - | 15 | Bug 1822052_: HTTPClient: actually set a timeout for requests 16 | 17 | .. _1822052: https://code.launchpad.net/bugs/1822052 18 | upgrade: 19 | - | 20 | The following Command Line Interface calls now take a ``--store`` 21 | option: 22 | 23 | * ``glance image-create`` 24 | * ``glance image-create-via-import`` 25 | * ``glance image-upload`` 26 | * ``glance image-import`` 27 | 28 | The value for this option is a store identifier. The list of 29 | available stores may be obtained from the ``glance stores-info`` 30 | command. 31 | 32 | - | 33 | The ``--backend`` option, available on some commands on an experimental 34 | basis since release 2.12.0, is no longer available. Use ``--store`` 35 | instead. 36 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_info.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import testtools 16 | from unittest import mock 17 | 18 | from glanceclient.v2 import info 19 | 20 | 21 | class TestController(testtools.TestCase): 22 | def setUp(self): 23 | super(TestController, self).setUp() 24 | self.fake_client = mock.MagicMock() 25 | self.info_controller = info.Controller(self.fake_client, None) 26 | 27 | def test_get_usage(self): 28 | fake_usage = { 29 | 'usage': { 30 | 'quota1': {'limit': 10, 'usage': 0}, 31 | 'quota2': {'limit': 20, 'usage': 5}, 32 | } 33 | } 34 | self.fake_client.get.return_value = (mock.MagicMock(), fake_usage) 35 | usage = self.info_controller.get_usage() 36 | self.assertEqual(fake_usage['usage'], usage) 37 | self.fake_client.get.assert_called_once_with('/v2/info/usage') 38 | -------------------------------------------------------------------------------- /tools/fix_ca_bundle.sh: -------------------------------------------------------------------------------- 1 | # When the functional tests are run in a devstack environment, we 2 | # need to make sure that the python-requests module installed by 3 | # tox in the test environment can find the distro-specific CA store 4 | # where the devstack certs have been installed. 5 | # 6 | # assumptions: 7 | # - devstack is running 8 | # - the devstack tls-proxy service is running 9 | # - the environment var OS_TESTENV_NAME is set in tox.ini (defaults 10 | # to 'functional' 11 | # 12 | # This code based on a function in devstack lib/tls 13 | function set_ca_bundle { 14 | local python_cmd=".tox/${OS_TESTENV_NAME:-functional}/bin/python" 15 | local capath=$($python_cmd -c $'try:\n from requests import certs\n print (certs.where())\nexcept ImportError: pass') 16 | # of course, each distro keeps the CA store in a different location 17 | local fedora_CA='/etc/pki/tls/certs/ca-bundle.crt' 18 | local ubuntu_CA='/etc/ssl/certs/ca-certificates.crt' 19 | local suse_CA='/etc/ssl/ca-bundle.pem' 20 | 21 | # the distro CA is rooted in /etc, so if ours isn't, we need to 22 | # change it 23 | if [[ ! $capath == "" && ! $capath =~ ^/etc/.* && ! -L $capath ]]; then 24 | if [[ -e $fedora_CA ]]; then 25 | rm -f $capath 26 | ln -s $fedora_CA $capath 27 | elif [[ -e $ubuntu_CA ]]; then 28 | rm -f $capath 29 | ln -s $ubuntu_CA $capath 30 | elif [[ -e $suse_CA ]]; then 31 | rm -f $capath 32 | ln -s $suse_CA $capath 33 | else 34 | echo "can't set CA bundle, expect tests to fail" 35 | fi 36 | fi 37 | } 38 | 39 | set_ca_bundle 40 | -------------------------------------------------------------------------------- /releasenotes/notes/multi-store-support-acc7ad0e7e8b6f99.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | This release adds client support for the Glance feature 5 | `multi-store backend support 6 | `_, 7 | introduced in the Rocky release. This feature allows end users 8 | to direct uploaded or imported image data to a particular backend 9 | when a cloud operator has configured the Image Service to use multiple 10 | backends. 11 | 12 | The available backends are discoverable by making the ``stores-info`` 13 | call, which will return a list of available backends. The list contains 14 | an identifier (``id``) and a ``description`` of each available 15 | backend. The default backend is indicated in this response. 16 | 17 | When uploading or importing an image, the glanceclient now accepts 18 | the ``--backend`` option. Its value must be the ``id`` of a backend 19 | configured in the cloud against which the call is being made. This 20 | option may also be configured by exporting the ``OS_IMAGE_BACKEND`` 21 | environment variable with the ``id`` of a configured backend as its 22 | value. 23 | 24 | Some other points to keep in mind: 25 | 26 | - If no backend is specified, the image data is stored in the 27 | default backend. 28 | - If the version of the Image Service API contacted does not 29 | support multi-store backends, the option is silently ignored 30 | and the image data is stored in the default backend. 31 | - If an invalid backend identifier is used, the glanceclient will 32 | exit with an error message. 33 | - Backend identifiers and their meanings are unique to each cloud. 34 | Consult the ``stores-info`` call and your cloud provider's 35 | documentation for details. 36 | -------------------------------------------------------------------------------- /glanceclient/v1/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from glanceclient.common import http 17 | from glanceclient.common import utils 18 | from glanceclient.v1 import image_members 19 | from glanceclient.v1 import images 20 | from glanceclient.v1 import versions 21 | 22 | 23 | class Client(object): 24 | """Client for the OpenStack Images v1 API. 25 | 26 | :param string endpoint: A user-supplied endpoint URL for the glance 27 | service. 28 | :param string token: Token for authentication. 29 | :param integer timeout: Allows customization of the timeout for client 30 | http requests. (optional) 31 | :param string language_header: Set Accept-Language header to be sent in 32 | requests to glance. 33 | """ 34 | 35 | def __init__(self, endpoint=None, **kwargs): 36 | """Initialize a new client for the Images v1 API.""" 37 | endpoint, self.version = utils.endpoint_version_from_url(endpoint, 1.0) 38 | self.http_client = http.get_http_client(endpoint=endpoint, **kwargs) 39 | self.images = images.ImageManager(self.http_client) 40 | self.image_members = image_members.ImageMemberManager(self.http_client) 41 | self.versions = versions.VersionManager(self.http_client) 42 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/README.rst: -------------------------------------------------------------------------------- 1 | ====================================== 2 | python-glanceclient functional testing 3 | ====================================== 4 | 5 | Idea 6 | ---- 7 | 8 | Run real client/server requests in the gate to catch issues which 9 | are difficult to catch with a purely unit test approach. 10 | 11 | Many projects (nova, keystone...) already have this form of testing in 12 | the gate. 13 | 14 | 15 | Testing Theory 16 | -------------- 17 | 18 | Since python-glanceclient has two uses, CLI and python API, we should 19 | have two sets of functional tests. CLI and python API. The python API 20 | tests should never use the CLI. But the CLI tests can use the python API 21 | where adding native support to the CLI for the required functionality 22 | would involve a non trivial amount of work. 23 | 24 | 25 | Functional Test Guidelines 26 | -------------------------- 27 | 28 | The functional tests require: 29 | 30 | 1) A working Glance/Keystone installation (for example, devstack) 31 | 2) A yaml file containing valid credentials 32 | 33 | If you are using devstack, a yaml file will have been created for you 34 | with the name /etc/openstack/clouds.yaml. The test code knows where 35 | to find it, so if you're using devstack, you don't need to do anything 36 | else. 37 | 38 | If you are not using devstack you should create a yaml file 39 | with the following format: 40 | 41 | clouds: 42 | devstack-admin: 43 | auth: 44 | auth_url: http://10.0.0.1:35357/v2.0 45 | password: example 46 | project_domain_id: default 47 | project_name: admin 48 | user_domain_id: default 49 | username: admin 50 | identity_api_version: '2.0' 51 | region_name: RegionOne 52 | 53 | The tests will look for a file named 'clouds.yaml' in the 54 | following locations (in this order, first found wins): 55 | 56 | * current directory 57 | * ~/.config/openstack 58 | * /etc/openstack 59 | 60 | You may also set the environment variable OS_CLIENT_CONFIG_FILE 61 | to the absolute pathname of a file and that location will be 62 | inserted at the front of the search list. 63 | -------------------------------------------------------------------------------- /glanceclient/v2/image_tags.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import warlock 17 | 18 | from glanceclient.common import utils 19 | from glanceclient.v2 import schemas 20 | 21 | 22 | class Controller(object): 23 | def __init__(self, http_client, schema_client): 24 | self.http_client = http_client 25 | self.schema_client = schema_client 26 | 27 | @utils.memoized_property 28 | def model(self): 29 | schema = self.schema_client.get('image') 30 | return warlock.model_factory(schema.raw(), 31 | base_class=schemas.SchemaBasedModel) 32 | 33 | @utils.add_req_id_to_object() 34 | def update(self, image_id, tag_value): 35 | """Update an image with the given tag. 36 | 37 | :param image_id: image to be updated with the given tag. 38 | :param tag_value: value of the tag. 39 | """ 40 | url = '/v2/images/%s/tags/%s' % (image_id, tag_value) 41 | resp, body = self.http_client.put(url) 42 | return (resp, body), resp 43 | 44 | @utils.add_req_id_to_object() 45 | def delete(self, image_id, tag_value): 46 | """Delete the tag associated with the given image. 47 | 48 | :param image_id: Image whose tag to be deleted. 49 | :param tag_value: tag value to be deleted. 50 | """ 51 | url = '/v2/images/%s/tags/%s' % (image_id, tag_value) 52 | resp, body = self.http_client.delete(url) 53 | return (resp, body), resp 54 | -------------------------------------------------------------------------------- /releasenotes/notes/3.6.0_Release-04d3b5017747290b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: | 3 | This version of python-glanceclient introduces new commands ``usage`` 4 | to fetch quota related usage information and ``cache-clear``, 5 | ``cache-delete``, ``cache-list`` and ``cache-queue`` for cache related 6 | operations. 7 | 8 | features: 9 | - | 10 | Adds support for ``usage`` command which will report the usage 11 | of unified limits configured. The following commands have been added to 12 | the command line interface: 13 | 14 | * ``usage`` - Get quota usage information. 15 | 16 | - | 17 | Client support has been added for the new caching functionality 18 | introduced into Glance in this cycle. This feature is only available in 19 | the Images API version 2 when the caching middleware is enabled in the 20 | Glance service that the client is contacting. The following commands have 21 | been added to the command line interface: 22 | 23 | * ``cache-clear`` - Delete all the images from cache and queued for caching 24 | * ``cache-delete`` - Delete the specified image from cache or from the queued 25 | list 26 | * ``cache-list`` - List all the images which are cached or being queued for 27 | caching 28 | * ``cache-queue`` - Queue specified image(s) for caching. 29 | 30 | - | 31 | Client side support has been added to fetch store specific configuration 32 | information. With sufficient permissions, this will display additional 33 | information about the stores. 34 | 35 | - | 36 | Client side support has been added to provide the facility of appending 37 | the tags while creating new multiple tags rather than overwriting the 38 | existing tags. 39 | 40 | upgrade: 41 | - | 42 | The following Command Line Interface call now takes ``--detail`` option: 43 | 44 | * | ``glance stores-info`` 45 | | The value for ``--detail`` option could be True or False. 46 | 47 | - | 48 | The following Command Line Interface call now takes ``--append`` option: 49 | 50 | * | ``glance md-tag-create-multiple`` 51 | | The value for ``--append`` option could be True or False. 52 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 33 | 9l4gwJ1ojNB2abF4LOEf5bU= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/expired-cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 34 | l3+dFHnWayuCdqcFwIl3m8i8FupFihz9+A== 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGVTCCBD2gAwIBAgIUEstxpjoCFDZo8K1Mmz7QIpYwSXIwDQYJKoZIhvcNAQEL 3 | BQAwgbgxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhTdGF0ZSBDQTELMAkGA1UEBwwC 4 | Q0ExGTAXBgNVBAoMEE9wZW5TdGFjayBDQSBPcmcxGjAYBgNVBAsMEU9wZW5TdGFj 5 | ayBUZXN0IENBMS0wKwYDVQQDDCRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB 6 | dXRob3JpdHkxIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMCAX 7 | DTIwMDQwNzEyMjYwMVoYDzI5OTkwMTAxMTIyNjAxWjCBuDELMAkGA1UEBhMCQVUx 8 | ETAPBgNVBAgMCFN0YXRlIENBMQswCQYDVQQHDAJDQTEZMBcGA1UECgwQT3BlblN0 9 | YWNrIENBIE9yZzEaMBgGA1UECwwRT3BlblN0YWNrIFRlc3QgQ0ExLTArBgNVBAMM 10 | JE9wZW5zdGFjayBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTEjMCEGCSqGSIb3 11 | DQEJARYUYWRtaW5AY2EuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC 12 | DwAwggIKAoICAQD6KfGpVSJsmhTgdbdovaaa3Qe4PeP+Dg9Y7muSggVQVlqUp3YB 13 | xUSo1RDoLyu1ci+KrNODr+kkD/Cbo8yJQCxqTzUpCw3tadFhUJOWvPaqdcYTA8R9 14 | p7Xjvw3Me7Q7xr4l0pCUIiz/kwxYxk+GCQyXzpXZm14zz+Qm8gz37eoW2jJfoyzA 15 | dB9Tp609Id7C6VHFCWZ2Zsa4+Ua/q+Pn7vLNJ61C2H5sfus8dtcGDzViDWwnWyHw 16 | spyR79rciV2yA3xeq09RvIx2SB1tc3S2Rxw7SmKeYcnkv6YplvhIG5QpErvp+URh 17 | De2wbbxzjFzJqFQO0Yh8IgTBIvQI02++lA8ZX84UDaxmrT92m8GfqQvb96em/H1k 18 | RKJTq0QqSC+BbGDeFxHkuOTJiOZm5Bnivpo0TAPwX6YqpadXARAFWw+fJiHCuFGr 19 | 6ltD7zgRnx6SR5WNRNWmTZQNx7wC2Bm0cJ2ec0Asn+bl93RVloaNtbFJhkaN555G 20 | GnUDLvxiwIN7aMGviJLte/qIhkKTtxD7zxyk+PQhokPAiz8J9P8INDd3GzMvcRPX 21 | ufDoXjSGjSLzjVPMhkFxXaHHylBdEAtHxROKz5wJnHqCnKlyyyv0nGBPQxFjT+rb 22 | G0HFn1JjodPBLrwooPttDgkEnq5yBpDkhFuYdZbgwjvQ4p5qrCp3EbDJ/QIDAQAB 23 | o1MwUTAdBgNVHQ4EFgQUkfKdH+sf7F/69HDbvtZ/ggVDCt4wHwYDVR0jBBgwFoAU 24 | kfKdH+sf7F/69HDbvtZ/ggVDCt4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B 25 | AQsFAAOCAgEAsUIwMa+2lR2a77AaVDDJikt4twylxJudJ4WEerFWOJeshCUCEGZF 26 | udlkLDMl9/f7XjpWPH2jc8c1xoxO63GZiLumk8mO9VzleGW6O03fQ9z6pOE1ueSg 27 | WnqHQYLbb5KizbiLen4xpa13cjZ2KFKJBkBEn2yOXXSOGP/yuWf1nQ1QumHCFFxf 28 | SRrJaaVqR8Ow6yPIjeCFT0IyeoGP5ihlxTvgSo+HeQx1wuYcBIG6clx4BGEgUa2N 29 | kaUF6v6EBl/mfX6YkhzvDygaS3men1XRgAkNFPXN9L7XVQdAh2hJSenq73seCJcc 30 | lD6Pr4U3VdWaTNYNZqpySspfJ6vp4XGNJFWZiaHPC5CALjgMzljdq9Xohedl2v0i 31 | zFZ4T3Zd+RT1941yD5rqlTnaqscpPnZpEUkULjH63v42/vLRSVkZOb6uSdOTL/c3 32 | bxDr4ZbN6cPY5As+XADPgOALuiQql+bRYOZOQL6i5lwtepfvmT6YGZZMk0WKhDcD 33 | C/cWX7z7834T2yYez2OmFdTr1UCmGd9IqTTQ01JTgr02y5lCN5J4KlG0SQBnQeQB 34 | Pj+gi1WElCIsBIX67WNCss5bpzn+T9cvMD5w2uG94ceT7jbIQ8LQ5x90kn+4HKr0 35 | PnD937DKLY+HPbm5l9CIhmsX+mWUOcqqWSvxBWeJSk4Qk60K3G/oQeY= 36 | -----END CERTIFICATE----- 37 | -------------------------------------------------------------------------------- /glanceclient/v2/cache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from glanceclient.common import utils 17 | from glanceclient import exc 18 | 19 | TARGET_VALUES = ('both', 'cache', 'queue') 20 | 21 | 22 | class Controller(object): 23 | def __init__(self, http_client): 24 | self.http_client = http_client 25 | 26 | def is_supported(self, version): 27 | if utils.has_version(self.http_client, version): 28 | return True 29 | else: 30 | raise exc.HTTPNotImplemented( 31 | 'Glance does not support image caching API (v2.14)') 32 | 33 | @utils.add_req_id_to_object() 34 | def list(self): 35 | if self.is_supported('v2.14'): 36 | url = '/v2/cache' 37 | resp, body = self.http_client.get(url) 38 | return body, resp 39 | 40 | @utils.add_req_id_to_object() 41 | def delete(self, image_id): 42 | if self.is_supported('v2.14'): 43 | resp, body = self.http_client.delete('/v2/cache/%s' % 44 | image_id) 45 | return body, resp 46 | 47 | @utils.add_req_id_to_object() 48 | def clear(self, target): 49 | if self.is_supported('v2.14'): 50 | url = '/v2/cache' 51 | headers = {} 52 | if target != "both": 53 | headers = {'x-image-cache-clear-target': target} 54 | resp, body = self.http_client.delete(url, headers=headers) 55 | return body, resp 56 | 57 | @utils.add_req_id_to_object() 58 | def queue(self, image_id): 59 | if self.is_supported('v2.14'): 60 | url = '/v2/cache/%s' % image_id 61 | resp, body = self.http_client.put(url) 62 | return body, resp 63 | -------------------------------------------------------------------------------- /releasenotes/notes/multihash-download-verification-596e91bf7b68e7db.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | This release adds verification of image data downloads using the Glance 5 | "multihash" feature introduced in the OpenStack Rocky release. When 6 | the ``os_hash_value`` is populated on an image, the glanceclient will 7 | verify this value by computing the hexdigest of the downloaded data 8 | using the algorithm specified by the image's ``os_hash_algo`` property. 9 | 10 | Because the secure hash algorithm specified is determined by the cloud 11 | provider, it is possible that the ``os_hash_algo`` may identify an 12 | algorithm not available in the version of the Python ``hashlib`` library 13 | used by the client. In such a case the download will fail due to an 14 | unsupported hash type. In the event this occurs, a new option, 15 | ``--allow-md5-fallback``, is introduced to the ``image-download`` command. 16 | When present, this option will allow the glanceclient to use the legacy 17 | MD5 checksum to verify the downloaded data if the secure hash algorithm 18 | specified by the ``os_hash_algo`` image property is not supported. 19 | 20 | Note that the fallback is *not* used in the case where the algorithm is 21 | supported but the hexdigest of the downloaded data does not match the 22 | ``os_hash_value``. In that case the download fails regardless of whether 23 | the option is present or not. 24 | 25 | Whether using the ``--allow-md5-fallback`` option is a good idea depends 26 | upon the user's expectations for the verification. MD5 is an insecure 27 | hashing algorithm, so if you are interested in making sure that the 28 | downloaded image data has not been replaced by a datastream carefully 29 | crafted to have the same MD5 checksum, then you should not use the 30 | fallback. If, however, you are using Glance in a trusted environment 31 | and your interest is simply to verify that no bits have flipped during 32 | the data transfer, the MD5 fallback is sufficient for that purpose. That 33 | being said, it is our recommendation that the multihash should be used 34 | whenever possible. 35 | security: 36 | - | 37 | This release of the glanceclient uses the Glance "multihash" feature, 38 | introduced in Rocky, to use a secure hashing algorithm to verify the 39 | integrity of downloaded data. Legacy images without the "multihash" 40 | image properties (``os_hash_algo`` and ``os_hash_value``) are verified 41 | using the MD5 ``checksum`` image property. 42 | -------------------------------------------------------------------------------- /doc/source/cli/glance.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | :program:`glance` CLI man page 3 | ============================== 4 | 5 | .. program:: glance 6 | .. highlight:: bash 7 | 8 | SYNOPSIS 9 | ======== 10 | 11 | :program:`glance` [options] [command-options] 12 | 13 | :program:`glance help` 14 | 15 | :program:`glance help` 16 | 17 | 18 | DESCRIPTION 19 | =========== 20 | 21 | The :program:`glance` command line utility interacts with OpenStack Images 22 | Service (Glance). 23 | 24 | In order to use the CLI, you must provide your OpenStack username, password, 25 | project (historically called tenant), and auth endpoint. You can use 26 | configuration options ``--os-username``, ``--os-password``, ``--os-project-id``, 27 | and ``--os-auth-url`` or set corresponding environment variables:: 28 | 29 | export OS_USERNAME=user 30 | export OS_PASSWORD=pass 31 | export OS_PROJECT_ID=b363706f891f48019483f8bd6503c54b 32 | export OS_AUTH_URL=http://auth.example.com:5000/v2.0 33 | 34 | The command line tool will attempt to reauthenticate using provided credentials 35 | for every request. You can override this behavior by manually supplying an auth 36 | token using ``--os-image-url`` and ``--os-auth-token`` or by setting 37 | corresponding environment variables:: 38 | 39 | export OS_IMAGE_URL=http://glance.example.org:9292/ 40 | export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 41 | 42 | You can select an API version to use by ``--os-image-api-version`` option or by 43 | setting corresponding environment variable:: 44 | 45 | export OS_IMAGE_API_VERSION=1 46 | 47 | Default Images API used is v2. 48 | 49 | OPTIONS 50 | ======= 51 | 52 | To get a list of available commands and options run:: 53 | 54 | glance help 55 | 56 | To get usage and options of a command:: 57 | 58 | glance help 59 | 60 | 61 | EXAMPLES 62 | ======== 63 | 64 | Get information about image-create command:: 65 | 66 | glance help image-create 67 | 68 | See available images:: 69 | 70 | glance image-list 71 | 72 | To get a verbose output including more fields in the image list response:: 73 | 74 | glance --verbose image-list 75 | 76 | Create new image:: 77 | 78 | glance image-create --name foo --disk-format=qcow2 \ 79 | --container-format=bare --visibility=public \ 80 | --file /tmp/foo.img 81 | 82 | Describe a specific image:: 83 | 84 | glance image-show 85 | 86 | 87 | BUGS 88 | ==== 89 | 90 | Glance client is hosted in Launchpad so you can view current bugs at 91 | https://bugs.launchpad.net/python-glanceclient/. 92 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Team and repository tags 3 | ======================== 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/python-glanceclient.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | :alt: The following tags have been asserted for Python bindings to the 8 | OpenStack Images API: 9 | "project:official", 10 | "stable:follows-policy", 11 | "vulnerability:managed". 12 | Follow the link for an explanation of these tags. 13 | .. NOTE(rosmaita): the alt text above will have to be updated when 14 | additional tags are asserted for python-glanceclient. (The SVG in the 15 | governance repo is updated automatically.) 16 | 17 | .. Change things from this point on 18 | 19 | =========================================== 20 | Python bindings to the OpenStack Images API 21 | =========================================== 22 | 23 | .. image:: https://img.shields.io/pypi/v/python-glanceclient.svg 24 | :target: https://pypi.org/project/python-glanceclient/ 25 | :alt: Latest Version 26 | 27 | This is a client library for Glance built on the OpenStack Images API. It provides a Python API (the ``glanceclient`` module) and a command-line tool (``glance``). This library fully supports the v1 Images API, while support for the v2 API is in progress. 28 | 29 | Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is in `Git `_. 30 | 31 | See release notes and more at ``_. 32 | 33 | * License: Apache License, Version 2.0 34 | * `PyPi`_ - package installation 35 | * `Online Documentation`_ 36 | * `Launchpad project`_ - release management 37 | * `Blueprints`_ - feature specifications 38 | * `Bugs`_ - issue tracking 39 | * `Source`_ 40 | * `Specs`_ 41 | * `How to Contribute`_ 42 | 43 | .. _PyPi: https://pypi.org/project/python-glanceclient 44 | .. _Online Documentation: https://docs.openstack.org/python-glanceclient/latest/ 45 | .. _Launchpad project: https://launchpad.net/python-glanceclient 46 | .. _Blueprints: https://blueprints.launchpad.net/python-glanceclient 47 | .. _Bugs: https://bugs.launchpad.net/python-glanceclient 48 | .. _Source: https://opendev.org/openstack/python-glanceclient 49 | .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html 50 | .. _Specs: https://specs.openstack.org/openstack/glance-specs/ 51 | .. _Release notes: https://docs.openstack.org/releasenotes/python-glanceclient 52 | -------------------------------------------------------------------------------- /glanceclient/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import warnings 17 | 18 | from oslo_utils import importutils 19 | 20 | from glanceclient.common import utils 21 | 22 | 23 | def Client(version=None, endpoint=None, session=None, *args, **kwargs): 24 | """Client for the OpenStack Images API. 25 | 26 | Generic client for the OpenStack Images API. See version classes 27 | for specific details. 28 | 29 | :param string version: The version of API to use. 30 | :param session: A keystoneauth1 session that should be used for transport. 31 | :type session: keystoneauth1.session.Session 32 | """ 33 | # FIXME(jamielennox): Add a deprecation warning if no session is passed. 34 | # Leaving it as an option until we can ensure nothing break when we switch. 35 | if session: 36 | if endpoint: 37 | kwargs.setdefault('endpoint_override', endpoint) 38 | 39 | if not version: 40 | __, version = utils.strip_version(endpoint) 41 | 42 | if not version: 43 | msg = ("You must provide a client version when using session") 44 | raise RuntimeError(msg) 45 | 46 | else: 47 | if version is not None: 48 | warnings.warn(("`version` keyword is being deprecated. Please pass" 49 | " the version as part of the URL. " 50 | "http://$HOST:$PORT/v$VERSION_NUMBER"), 51 | DeprecationWarning) 52 | 53 | endpoint, url_version = utils.strip_version(endpoint) 54 | version = version or url_version 55 | 56 | if not version: 57 | msg = ("Please provide either the version or an url with the form " 58 | "http://$HOST:$PORT/v$VERSION_NUMBER") 59 | raise RuntimeError(msg) 60 | 61 | module = importutils.import_versioned_module('glanceclient', int(version), 62 | 'client') 63 | client_class = getattr(module, 'Client') 64 | return client_class(endpoint, *args, session=session, **kwargs) 65 | -------------------------------------------------------------------------------- /glanceclient/v2/image_members.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import warlock 17 | 18 | from glanceclient.common import utils 19 | from glanceclient.v2 import schemas 20 | 21 | 22 | MEMBER_STATUS_VALUES = ('accepted', 'rejected', 'pending') 23 | 24 | 25 | class Controller(object): 26 | def __init__(self, http_client, schema_client): 27 | self.http_client = http_client 28 | self.schema_client = schema_client 29 | 30 | @utils.memoized_property 31 | def model(self): 32 | schema = self.schema_client.get('member') 33 | return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel) 34 | 35 | @utils.add_req_id_to_generator() 36 | def list(self, image_id): 37 | url = '/v2/images/%s/members' % image_id 38 | resp, body = self.http_client.get(url) 39 | for member in body['members']: 40 | yield self.model(member), resp 41 | 42 | @utils.add_req_id_to_object() 43 | def get(self, image_id, member_id): 44 | url = '/v2/images/%s/members/%s' % (image_id, member_id) 45 | resp, member = self.http_client.get(url) 46 | return self.model(member), resp 47 | 48 | @utils.add_req_id_to_object() 49 | def delete(self, image_id, member_id): 50 | resp, body = self.http_client.delete('/v2/images/%s/members/%s' % 51 | (image_id, member_id)) 52 | return (resp, body), resp 53 | 54 | @utils.add_req_id_to_object() 55 | def update(self, image_id, member_id, member_status): 56 | url = '/v2/images/%s/members/%s' % (image_id, member_id) 57 | body = {'status': member_status} 58 | resp, updated_member = self.http_client.put(url, data=body) 59 | return self.model(updated_member), resp 60 | 61 | @utils.add_req_id_to_object() 62 | def create(self, image_id, member_id): 63 | url = '/v2/images/%s/members' % image_id 64 | body = {'member': member_id} 65 | resp, created_member = self.http_client.post(url, data=body) 66 | return self.model(created_member), resp 67 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_versions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # Copyright 2015 Huawei Corp. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import testtools 18 | 19 | from glanceclient.tests import utils 20 | from glanceclient.v2 import versions 21 | 22 | fixtures = { 23 | '/versions': { 24 | 'GET': ( 25 | {}, 26 | {"versions": [ 27 | { 28 | "status": "EXPERIMENTAL", 29 | "id": "v3.0", 30 | "links": [ 31 | { 32 | "href": "http://10.229.45.145:9292/v3/", 33 | "rel": "self" 34 | } 35 | ] 36 | }, 37 | { 38 | "status": "CURRENT", 39 | "id": "v2.3", 40 | "links": [ 41 | { 42 | "href": "http://10.229.45.145:9292/v2/", 43 | "rel": "self" 44 | } 45 | ] 46 | }, 47 | { 48 | "status": "SUPPORTED", 49 | "id": "v1.0", 50 | "links": [ 51 | { 52 | "href": "http://10.229.45.145:9292/v1/", 53 | "rel": "self" 54 | } 55 | ] 56 | } 57 | ]} 58 | ) 59 | } 60 | } 61 | 62 | 63 | class TestVersions(testtools.TestCase): 64 | 65 | def setUp(self): 66 | super(TestVersions, self).setUp() 67 | self.api = utils.FakeAPI(fixtures) 68 | self.controller = versions.VersionController(self.api) 69 | 70 | def test_version_list(self): 71 | version = list(self.controller.list()) 72 | self.assertEqual('v3.0', version[0]['id']) 73 | self.assertEqual('EXPERIMENTAL', version[0]['status']) 74 | self.assertEqual([{"href": "http://10.229.45.145:9292/v3/", 75 | "rel": "self"}], version[0]['links']) 76 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39,pep8 3 | minversion = 3.18.0 4 | 5 | [testenv] 6 | usedevelop = True 7 | setenv = OS_STDOUT_NOCAPTURE=False 8 | OS_STDERR_NOCAPTURE=False 9 | PYTHONDONTWRITEBYTECODE=1 10 | 11 | # Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might 12 | # still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the 13 | # first one that is defined. If none of them is defined, we fallback to the 14 | # default value. 15 | deps = 16 | -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} 17 | -r{toxinidir}/requirements.txt 18 | -r{toxinidir}/test-requirements.txt 19 | commands = stestr run --slowest {posargs} 20 | 21 | [testenv:pep8] 22 | basepython = python3 23 | commands = flake8 24 | 25 | [testenv:venv] 26 | basepython = python3 27 | commands = {posargs} 28 | 29 | [pbr] 30 | warnerror = True 31 | 32 | [testenv:functional] 33 | # See glanceclient/tests/functional/README.rst 34 | # for information on running the functional tests. 35 | setenv = 36 | OS_TEST_PATH = ./glanceclient/tests/functional/v2 37 | OS_TESTENV_NAME = {envname} 38 | allowlist_externals = 39 | bash 40 | commands = 41 | bash tools/fix_ca_bundle.sh 42 | stestr run --slowest {posargs} 43 | 44 | [testenv:cover] 45 | basepython = python3 46 | setenv = 47 | PYTHON=coverage run --source glanceclient --parallel-mode 48 | commands = 49 | stestr run {posargs} 50 | coverage combine 51 | coverage html -d cover 52 | coverage xml -o cover/coverage.xml 53 | 54 | [testenv:docs] 55 | basepython = python3 56 | deps = 57 | -r{toxinidir}/requirements.txt 58 | -r{toxinidir}/doc/requirements.txt 59 | commands = 60 | sphinx-build -W -b html doc/source doc/build/html 61 | sphinx-build -W -b man doc/source doc/build/man 62 | 63 | [testenv:releasenotes] 64 | basepython = python3 65 | # Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might 66 | # still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the 67 | # first one that is defined. If none of them is defined, we fallback to the 68 | # default value. 69 | deps = 70 | -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} 71 | -r{toxinidir}/doc/requirements.txt 72 | commands = 73 | sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html 74 | 75 | [flake8] 76 | # E731 skipped as assign a lambda expression 77 | # W504 line break after binary operator 78 | ignore = E731,W504 79 | show-source = True 80 | exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv 81 | 82 | [hacking] 83 | import_exceptions = glanceclient._i18n 84 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # Copyright (C) 2013 Yahoo! Inc. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import testtools 18 | 19 | from glanceclient.v1.apiclient import base 20 | 21 | 22 | class TestBase(testtools.TestCase): 23 | 24 | def test_resource_repr(self): 25 | r = base.Resource(None, dict(foo="bar", baz="spam")) 26 | self.assertEqual("", repr(r)) 27 | 28 | def test_getid(self): 29 | self.assertEqual(4, base.getid(4)) 30 | 31 | class TmpObject(object): 32 | id = 4 33 | self.assertEqual(4, base.getid(TmpObject)) 34 | 35 | def test_two_resources_with_same_id_are_not_equal(self): 36 | # Two resources with same ID: never equal if their info is not equal 37 | r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) 38 | r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) 39 | self.assertNotEqual(r1, r2) 40 | 41 | def test_two_resources_with_same_id_and_info_are_equal(self): 42 | # Two resources with same ID: equal if their info is equal 43 | r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) 44 | r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) 45 | self.assertEqual(r1, r2) 46 | 47 | def test_two_resources_with_eq_info_are_equal(self): 48 | # Two resources with no ID: equal if their info is equal 49 | r1 = base.Resource(None, {'name': 'joe', 'age': 12}) 50 | r2 = base.Resource(None, {'name': 'joe', 'age': 12}) 51 | self.assertEqual(r1, r2) 52 | 53 | def test_two_resources_with_diff_id_are_not_equal(self): 54 | # Two resources with diff ID: not equal 55 | r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) 56 | r2 = base.Resource(None, {'id': 2, 'name': 'hello'}) 57 | self.assertNotEqual(r1, r2) 58 | 59 | def test_two_resources_with_not_eq_info_are_not_equal(self): 60 | # Two resources with no ID: not equal if their info is not equal 61 | r1 = base.Resource(None, {'name': 'bill', 'age': 21}) 62 | r2 = base.Resource(None, {'name': 'joe', 'age': 12}) 63 | self.assertNotEqual(r1, r2) 64 | -------------------------------------------------------------------------------- /doc/source/reference/apiv2.rst: -------------------------------------------------------------------------------- 1 | Python API v2 2 | ============= 3 | 4 | These Identity Service credentials can be used to authenticate:: 5 | 6 | * auth_url: Identity Service endpoint for authorization 7 | * username: name of user 8 | * password: user's password 9 | * project_{name|id}: name or ID of project 10 | 11 | Also the following parameters are required when using the Identity API v3:: 12 | 13 | * user_domain_{name|id}: name or ID of a domain the user belongs to 14 | * project_domain_{name|id}: name or ID for a domain the project belongs to 15 | 16 | To create a client:: 17 | 18 | from keystoneauth1 import loading 19 | from keystoneauth1 import session 20 | from glanceclient import Client 21 | 22 | loader = loading.get_plugin_loader('password') 23 | auth = loader.load_from_options( 24 | auth_url=AUTH_URL, 25 | username=USERNAME, 26 | password=PASSWORD, 27 | project_id=PROJECT_ID) 28 | session = session.Session(auth=auth) 29 | 30 | glance = Client('2', session=session) 31 | 32 | 33 | Create 34 | ------ 35 | Create a new image:: 36 | 37 | image = glance.images.create(name="myNewImage") 38 | glance.images.upload(image.id, open('/tmp/myimage.iso', 'rb')) 39 | 40 | Show 41 | ---- 42 | Describe a specific image:: 43 | 44 | glance.images.get(image.id) 45 | 46 | Update 47 | ------ 48 | Update a specific image:: 49 | 50 | # update with a list of image attribute names and their new values 51 | glance.images.update(image.id, name="myNewImageName") 52 | 53 | Custom Properties 54 | ----------------- 55 | Set a custom property on an image:: 56 | 57 | # set an arbitrary property on an image 58 | glance.images.update(image.id, my_custom_property='value') 59 | 60 | Remove a custom property from an image:: 61 | 62 | # remove the custom property 'my_custom_property' 63 | glance.images.update(image.id, remove_props=['my_custom_property']) 64 | 65 | Delete 66 | ------ 67 | Delete specified image(s):: 68 | 69 | glance.images.delete(image.id) 70 | 71 | List 72 | ---- 73 | List images you can access:: 74 | 75 | for image in glance.images.list(): 76 | print image 77 | 78 | Download 79 | -------- 80 | Download a specific image:: 81 | 82 | d = glance.images.data(image.id) 83 | 84 | Share an Image 85 | -------------- 86 | Share a specific image with a tenant:: 87 | 88 | glance.image_members.create(image_id, member_id) 89 | 90 | Remove a Share 91 | -------------- 92 | Remove a shared image from a tenant:: 93 | 94 | glance.image_members.delete(image_id, member_id) 95 | 96 | List Sharings 97 | ------------- 98 | Describe sharing permissions by image or tenant:: 99 | 100 | glance.image_members.list(image_id) 101 | 102 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_tags.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient.tests.unit.v2 import base 19 | from glanceclient.tests import utils 20 | from glanceclient.v2 import image_tags 21 | 22 | 23 | IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1' 24 | TAG = 'tag01' 25 | 26 | 27 | data_fixtures = { 28 | '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG): { 29 | 'DELETE': ( 30 | {}, 31 | None, 32 | ), 33 | 'PUT': ( 34 | {}, 35 | { 36 | 'image_id': IMAGE, 37 | 'tag_value': TAG 38 | } 39 | ), 40 | } 41 | } 42 | 43 | schema_fixtures = { 44 | 'tag': { 45 | 'GET': ( 46 | {}, 47 | {'name': 'image', 'properties': {'image_id': {}, 'tags': {}}} 48 | ) 49 | } 50 | } 51 | 52 | 53 | class TestController(testtools.TestCase): 54 | def setUp(self): 55 | super(TestController, self).setUp() 56 | self.api = utils.FakeAPI(data_fixtures) 57 | self.schema_api = utils.FakeSchemaAPI(schema_fixtures) 58 | self.controller = base.BaseController(self.api, self.schema_api, 59 | image_tags.Controller) 60 | 61 | def test_update_image_tag(self): 62 | image_id = IMAGE 63 | tag_value = TAG 64 | self.controller.update(image_id, tag_value) 65 | expect = [ 66 | ('PUT', 67 | '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, 68 | tag_value=TAG), 69 | {}, 70 | None)] 71 | self.assertEqual(expect, self.api.calls) 72 | 73 | def test_delete_image_tag(self): 74 | image_id = IMAGE 75 | tag_value = TAG 76 | self.controller.delete(image_id, tag_value) 77 | expect = [ 78 | ('DELETE', 79 | '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, 80 | tag_value=TAG), 81 | {}, 82 | None)] 83 | self.assertEqual(expect, self.api.calls) 84 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/wildcard-san-certificate.crt: -------------------------------------------------------------------------------- 1 | #Certificate: 2 | # Data: 3 | # Version: 3 (0x2) 4 | # Serial Number: 11990626514780340979 (0xa66743493fdcc2f3) 5 | # Signature Algorithm: sha1WithRSAEncryption 6 | # Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 7 | # Validity 8 | # Not Before: Dec 10 15:31:22 2013 GMT 9 | # Not After : Nov 16 15:31:22 2113 GMT 10 | # Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 11 | # Subject Public Key Info: 12 | # Public Key Algorithm: rsaEncryption 13 | # Public-Key: (2048 bit) 14 | # Modulus: 15 | # 00:ca:6b:07:73:53:24:45:74:05:a5:2a:27:bd:3e: 16 | # . 17 | # . 18 | # . 19 | # Exponent: 65537 (0x10001) 20 | # X509v3 extensions: 21 | # X509v3 Key Usage: 22 | # Key Encipherment, Data Encipherment 23 | # X509v3 Extended Key Usage: 24 | # TLS Web Server Authentication 25 | # X509v3 Subject Alternative Name: 26 | # DNS:foo.example.net, DNS:*.example.com 27 | # Signature Algorithm: sha1WithRSAEncryption 28 | # 7e:41:69:da:f4:3c:06:d6:83:c6:f2:db:df:37:f1:ac:fa:f5: 29 | # . 30 | # . 31 | # . 32 | -----BEGIN CERTIFICATE----- 33 | MIIDxDCCAqygAwIBAgIJAKZnQ0k/3MLzMA0GCSqGSIb3DQEBBQUAMHgxCzAJBgNV 34 | BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUxMRswGQYDVQQKExJP 35 | cGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFjayBUZXN0IFVuaXQx 36 | EDAOBgNVBAMTBzAuMC4wLjAwIBcNMTMxMjEwMTUzMTIyWhgPMjExMzExMTYxNTMx 37 | MjJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUx 38 | MRswGQYDVQQKExJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFj 39 | ayBUZXN0IFVuaXQxEDAOBgNVBAMTBzAuMC4wLjAwggEiMA0GCSqGSIb3DQEBAQUA 40 | A4IBDwAwggEKAoIBAQDKawdzUyRFdAWlKie9Pn10j7frffN+z1gEMluK2CtDEwv9 41 | kbD4uS/Kz4dujfTx03mdyNfiMVlOM+YJm/qeLLSdJyFyvZ9Y3WmJ+vT2RGlMMhLd 42 | /wEnMRrTYLL39pwI6z+gyw+4D78Pyv/OXy02IA6WtVEefYSx1vmVngb3pL+iBzhO 43 | 8CZXNI6lqrFhh+Hr4iMkYMtY1vTnwezAL6p64E/ZAFNPYCEJlacESTLQ4VZYniHc 44 | QTgnE1czlI1vxlIk1KDXAzUGeeopZecRih9qlTxtOpklqEciQEE+sHtPcvyvdRE9 45 | Bdyx5rNSALLIcXs0ViJE1RPlw3fjdBoDIOygqvX1AgMBAAGjTzBNMAsGA1UdDwQE 46 | AwIEMDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAggg9mb28uZXhhbXBs 47 | ZS5uZXSCDSouZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAH5Badr0PAbW 48 | g8by29838az69Raul5IkpZQ5V3O1NaNNWxvmF1q8zFFqqGK5ktXJAwGiwnYEBb30 49 | Zfrr+eFIEERzBthSJkWlP8NG+2ooMyg50femp+asAvW+KYYefJW8KaXTsznMsAFy 50 | z1agcWVYVZ4H9PwunEYn/rM1krLEe4Cagsw5nmf8VqZg+hHtw930q8cRzgDsZdfA 51 | jVK6dWdmzmLCUTL1GKCeNriDw1jIeFvNufC+Q3orH7xBx4VL+NV5ORWdNY/B8q1b 52 | mFHdzbuZX6v39+2ww6aZqG2orfxUocc/5Ox6fXqenKPI3moeHS6Ktesq7sEQSJ6H 53 | QZFsTuT/124= 54 | -----END CERTIFICATE----- 55 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v1/test_versions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # Copyright 2015 Huawei Corp. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import testtools 18 | 19 | from glanceclient.tests import utils 20 | import glanceclient.v1.versions 21 | 22 | 23 | fixtures = { 24 | '/versions': { 25 | 'GET': ( 26 | {}, 27 | {"versions": [ 28 | { 29 | "status": "EXPERIMENTAL", 30 | "id": "v3.0", 31 | "links": [ 32 | { 33 | "href": "http://10.229.45.145:9292/v3/", 34 | "rel": "self" 35 | } 36 | ] 37 | }, 38 | { 39 | "status": "CURRENT", 40 | "id": "v2.3", 41 | "links": [ 42 | { 43 | "href": "http://10.229.45.145:9292/v2/", 44 | "rel": "self" 45 | } 46 | ] 47 | }, 48 | { 49 | "status": "SUPPORTED", 50 | "id": "v1.0", 51 | "links": [ 52 | { 53 | "href": "http://10.229.45.145:9292/v1/", 54 | "rel": "self" 55 | } 56 | ] 57 | } 58 | ]} 59 | ) 60 | } 61 | } 62 | 63 | 64 | class TestVersions(testtools.TestCase): 65 | 66 | def setUp(self): 67 | super(TestVersions, self).setUp() 68 | self.api = utils.FakeAPI(fixtures) 69 | self.mgr = glanceclient.v1.versions.VersionManager(self.api) 70 | 71 | def test_version_list(self): 72 | versions = self.mgr.list() 73 | expect = [('GET', '/versions', {}, None)] 74 | self.assertEqual(expect, self.api.calls) 75 | self.assertEqual(3, len(versions)) 76 | self.assertEqual('v3.0', versions[0]['id']) 77 | self.assertEqual('EXPERIMENTAL', versions[0]['status']) 78 | self.assertEqual([{"href": "http://10.229.45.145:9292/v3/", 79 | "rel": "self"}], versions[0]['links']) 80 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/v2/test_http_headers.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from glanceclient.tests.functional import base 14 | import time 15 | 16 | 17 | IMAGE = {"protected": False, 18 | "disk_format": "qcow2", 19 | "name": "glance_functional_test_image.img", 20 | "visibility": "private", 21 | "container_format": "bare"} 22 | 23 | 24 | class HttpHeadersTest(base.ClientTestBase): 25 | def test_encode_headers_python(self): 26 | """Test proper handling of Content-Type headers. 27 | 28 | encode_headers() must be called as late as possible before a 29 | request is sent. If this principle is violated, and if any 30 | changes are made to the headers between encode_headers() and the 31 | actual request (for instance a call to 32 | _set_common_request_kwargs()), and if you're trying to set a 33 | Content-Type that is not equal to application/octet-stream (the 34 | default), it is entirely possible that you'll end up with two 35 | Content-Type headers defined (yours plus 36 | application/octet-stream). The request will go out the door with 37 | only one of them chosen seemingly at random. 38 | 39 | This test uses a call to update() because it sets a header such 40 | as the following (this example may be subject to change): 41 | Content-Type: application/openstack-images-v2.1-json-patch 42 | 43 | This situation only occurs in python3. This test will never fail 44 | in python2. 45 | 46 | There is no test against the CLI because it swallows the error. 47 | """ 48 | # the failure is intermittent - try up to 6 times 49 | for attempt in range(0, 6): 50 | glanceclient = self.glance_pyclient() 51 | image = glanceclient.find(IMAGE["name"]) 52 | if image: 53 | glanceclient.glance.images.delete(image.id) 54 | image = glanceclient.glance.images.create(name=IMAGE["name"]) 55 | self.assertTrue(image.status == "queued") 56 | try: 57 | image = glanceclient.glance.images.update(image.id, 58 | disk_format="qcow2") 59 | except Exception as e: 60 | self.assertNotIn("415 Unsupported Media Type", e.details) 61 | time.sleep(5) 62 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/test_exc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | import testtools 16 | from unittest import mock 17 | 18 | from glanceclient import exc 19 | 20 | HTML_MSG = """ 21 | 22 | 404 Entity Not Found 23 | 24 | 25 |

404 Entity Not Found

26 | Entity could not be found 27 |

28 | 29 | """ 30 | 31 | 32 | class TestHTTPExceptions(testtools.TestCase): 33 | def test_from_response(self): 34 | """exc.from_response should return instance of an HTTP exception.""" 35 | mock_resp = mock.Mock() 36 | mock_resp.status_code = 400 37 | out = exc.from_response(mock_resp) 38 | self.assertIsInstance(out, exc.HTTPBadRequest) 39 | 40 | def test_handles_json(self): 41 | """exc.from_response should not print JSON.""" 42 | mock_resp = mock.Mock() 43 | mock_resp.status_code = 413 44 | mock_resp.json.return_value = { 45 | "overLimit": { 46 | "code": 413, 47 | "message": "OverLimit Retry...", 48 | "details": "Error Details...", 49 | "retryAt": "2014-12-03T13:33:06Z" 50 | } 51 | } 52 | mock_resp.headers = { 53 | "content-type": "application/json" 54 | } 55 | err = exc.from_response(mock_resp, "Non-empty body") 56 | self.assertIsInstance(err, exc.HTTPOverLimit) 57 | self.assertEqual("OverLimit Retry...", err.details) 58 | 59 | def test_handles_html(self): 60 | """exc.from_response should not print HTML.""" 61 | mock_resp = mock.Mock() 62 | mock_resp.status_code = 404 63 | mock_resp.text = HTML_MSG 64 | mock_resp.headers = { 65 | "content-type": "text/html" 66 | } 67 | err = exc.from_response(mock_resp, HTML_MSG) 68 | self.assertIsInstance(err, exc.HTTPNotFound) 69 | self.assertEqual("404 Entity Not Found: Entity could not be found", 70 | err.details) 71 | 72 | def test_format_no_content_type(self): 73 | mock_resp = mock.Mock() 74 | mock_resp.status_code = 400 75 | mock_resp.headers = {'content-type': 'application/octet-stream'} 76 | body = b'Error \n\n' 77 | err = exc.from_response(mock_resp, body) 78 | self.assertEqual('Error \n', err.details) 79 | -------------------------------------------------------------------------------- /glanceclient/v2/resource_type_schema.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | # NOTE(flaper87): Keep a copy of the current default schema so that 18 | # we can react on cases where there's no connection to an OpenStack 19 | # deployment. See #1481729 20 | BASE_SCHEMA = { 21 | "additionalProperties": False, 22 | "required": ["name"], 23 | "name": "resource_type_association", 24 | "properties": { 25 | "name": { 26 | "type": "string", 27 | "description": "Resource type names should be aligned with Heat " 28 | "resource types whenever possible: https://docs." 29 | "openstack.org/heat/latest/template_guide/" 30 | "openstack.html", 31 | "maxLength": 80 32 | 33 | }, 34 | "prefix": { 35 | "type": "string", 36 | "description": "Specifies the prefix to use for the given resource" 37 | " type. Any properties in the namespace should be" 38 | " prefixed with this prefix when being applied to" 39 | " the specified resource type. Must include prefix" 40 | " separator (e.g. a colon :).", 41 | "maxLength": 80 42 | }, 43 | "properties_target": { 44 | "type": "string", 45 | "description": "Some resource types allow more than one key / " 46 | "value pair per instance. For example, Cinder " 47 | "allows user and image metadata on volumes. Only " 48 | "the image properties metadata is evaluated by Nova" 49 | " (scheduling or drivers). This property allows a " 50 | "namespace target to remove the ambiguity.", 51 | "maxLength": 80 52 | }, 53 | "created_at": { 54 | "type": "string", 55 | "readOnly": True, 56 | "description": "Date and time of resource type association.", 57 | "format": "date-time" 58 | }, 59 | "updated_at": { 60 | "type": "string", 61 | "readOnly": True, 62 | "description": "Date and time of the last resource type " 63 | "association modification.", 64 | "format": "date-time" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/test_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Red Hat, Inc. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient import client 19 | from glanceclient import v1 20 | from glanceclient import v2 21 | 22 | 23 | class ClientTest(testtools.TestCase): 24 | 25 | def test_no_endpoint_error(self): 26 | self.assertRaises(ValueError, client.Client, None) 27 | 28 | def test_endpoint(self): 29 | gc = client.Client(1, "http://example.com") 30 | self.assertEqual("http://example.com", gc.http_client.endpoint) 31 | self.assertIsInstance(gc, v1.client.Client) 32 | 33 | def test_versioned_endpoint(self): 34 | gc = client.Client(1, "http://example.com/v2") 35 | self.assertEqual("http://example.com", gc.http_client.endpoint) 36 | self.assertIsInstance(gc, v1.client.Client) 37 | 38 | def test_versioned_endpoint_no_version(self): 39 | gc = client.Client(endpoint="http://example.com/v2") 40 | self.assertEqual("http://example.com", gc.http_client.endpoint) 41 | self.assertIsInstance(gc, v2.client.Client) 42 | 43 | def test_versioned_endpoint_with_minor_revision(self): 44 | gc = client.Client(2.2, "http://example.com/v2.1") 45 | self.assertEqual("http://example.com", gc.http_client.endpoint) 46 | self.assertIsInstance(gc, v2.client.Client) 47 | 48 | def test_endpoint_with_version_hostname(self): 49 | gc = client.Client(2, "http://v1.example.com") 50 | self.assertEqual("http://v1.example.com", gc.http_client.endpoint) 51 | self.assertIsInstance(gc, v2.client.Client) 52 | 53 | def test_versioned_endpoint_with_version_hostname_v2(self): 54 | gc = client.Client(endpoint="http://v1.example.com/v2") 55 | self.assertEqual("http://v1.example.com", gc.http_client.endpoint) 56 | self.assertIsInstance(gc, v2.client.Client) 57 | 58 | def test_versioned_endpoint_with_version_hostname_v1(self): 59 | gc = client.Client(endpoint="http://v2.example.com/v1") 60 | self.assertEqual("http://v2.example.com", gc.http_client.endpoint) 61 | self.assertIsInstance(gc, v1.client.Client) 62 | 63 | def test_versioned_endpoint_with_minor_revision_and_version_hostname(self): 64 | gc = client.Client(endpoint="http://v1.example.com/v2.1") 65 | self.assertEqual("http://v1.example.com", gc.http_client.endpoint) 66 | self.assertIsInstance(gc, v2.client.Client) 67 | -------------------------------------------------------------------------------- /glanceclient/v2/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | from glanceclient.common import http 18 | from glanceclient.common import utils 19 | from glanceclient.v2 import cache 20 | from glanceclient.v2 import image_members 21 | from glanceclient.v2 import image_tags 22 | from glanceclient.v2 import images 23 | from glanceclient.v2 import info 24 | from glanceclient.v2 import metadefs 25 | from glanceclient.v2 import schemas 26 | from glanceclient.v2 import tasks 27 | from glanceclient.v2 import versions 28 | 29 | 30 | class Client(object): 31 | """Client for the OpenStack Images v2 API. 32 | 33 | :param string endpoint: A user-supplied endpoint URL for the glance 34 | service. 35 | :param string token: Token for authentication. 36 | :param integer timeout: Allows customization of the timeout for client 37 | http requests. (optional) 38 | :param string language_header: Set Accept-Language header to be sent in 39 | requests to glance. 40 | """ 41 | 42 | def __init__(self, endpoint=None, **kwargs): 43 | self.endpoint_provided = endpoint is not None 44 | endpoint, self.version = utils.endpoint_version_from_url(endpoint, 2.0) 45 | self.http_client = http.get_http_client(endpoint=endpoint, **kwargs) 46 | self.schemas = schemas.Controller(self.http_client) 47 | 48 | self.images = images.Controller(self.http_client, self.schemas) 49 | self.image_tags = image_tags.Controller(self.http_client, 50 | self.schemas) 51 | self.image_members = image_members.Controller(self.http_client, 52 | self.schemas) 53 | 54 | self.info = info.Controller(self.http_client, self.schemas) 55 | 56 | self.tasks = tasks.Controller(self.http_client, self.schemas) 57 | 58 | self.metadefs_resource_type = ( 59 | metadefs.ResourceTypeController(self.http_client, self.schemas)) 60 | 61 | self.metadefs_property = ( 62 | metadefs.PropertyController(self.http_client, self.schemas)) 63 | 64 | self.metadefs_object = ( 65 | metadefs.ObjectController(self.http_client, self.schemas)) 66 | 67 | self.metadefs_tag = ( 68 | metadefs.TagController(self.http_client, self.schemas)) 69 | 70 | self.metadefs_namespace = ( 71 | metadefs.NamespaceController(self.http_client, self.schemas)) 72 | 73 | self.versions = versions.VersionController(self.http_client) 74 | 75 | self.cache = cache.Controller(self.http_client) 76 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/test_progressbar.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import io 17 | import sys 18 | 19 | import requests 20 | import testtools 21 | 22 | from glanceclient.common import progressbar 23 | from glanceclient.common import utils 24 | from glanceclient.tests import utils as test_utils 25 | 26 | 27 | class TestProgressBarWrapper(testtools.TestCase): 28 | 29 | def test_iter_iterator_display_progress_bar(self): 30 | size = 100 31 | # create fake response object to return request-id with iterator 32 | resp = requests.Response() 33 | resp.headers['x-openstack-request-id'] = 'req-1234' 34 | iterator_with_len = utils.IterableWithLength(iter('X' * 100), size) 35 | requestid_proxy = utils.RequestIdProxy((iterator_with_len, resp)) 36 | saved_stdout = sys.stdout 37 | try: 38 | sys.stdout = output = test_utils.FakeTTYStdout() 39 | # Consume iterator. 40 | data = list(progressbar.VerboseIteratorWrapper(requestid_proxy, 41 | size)) 42 | self.assertEqual(['X'] * 100, data) 43 | self.assertEqual( 44 | '[%s>] 100%%\n' % ('=' * 29), 45 | output.getvalue() 46 | ) 47 | finally: 48 | sys.stdout = saved_stdout 49 | 50 | def test_iter_file_display_progress_bar(self): 51 | size = 98304 52 | file_obj = io.StringIO('X' * size) 53 | saved_stdout = sys.stdout 54 | try: 55 | sys.stdout = output = test_utils.FakeTTYStdout() 56 | file_obj = progressbar.VerboseFileWrapper(file_obj, size) 57 | chunksize = 1024 58 | chunk = file_obj.read(chunksize) 59 | while chunk: 60 | chunk = file_obj.read(chunksize) 61 | self.assertEqual( 62 | '[%s>] 100%%\n' % ('=' * 29), 63 | output.getvalue() 64 | ) 65 | finally: 66 | sys.stdout = saved_stdout 67 | 68 | def test_iter_file_no_tty(self): 69 | size = 98304 70 | file_obj = io.StringIO('X' * size) 71 | saved_stdout = sys.stdout 72 | try: 73 | sys.stdout = output = test_utils.FakeNoTTYStdout() 74 | file_obj = progressbar.VerboseFileWrapper(file_obj, size) 75 | chunksize = 1024 76 | chunk = file_obj.read(chunksize) 77 | while chunk: 78 | chunk = file_obj.read(chunksize) 79 | # If stdout is not a tty progress bar should do nothing. 80 | self.assertEqual('', output.getvalue()) 81 | finally: 82 | sys.stdout = saved_stdout 83 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | import sys 18 | 19 | 20 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 21 | '..', '..'))) 22 | 23 | # -- General configuration ---------------------------------------------------- 24 | 25 | # Add any Sphinx extension module names here, as strings. They can be 26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 27 | extensions = [ 28 | 'openstackdocstheme', 29 | 'sphinxcontrib.apidoc', 30 | ] 31 | 32 | # sphinxcontrib.apidoc options 33 | apidoc_module_dir = '../../glanceclient' 34 | apidoc_output_dir = 'reference/api' 35 | apidoc_excluded_paths = [ 36 | 'tests/*', 37 | 'tests'] 38 | apidoc_separate_modules = True 39 | 40 | # openstackdocstheme options 41 | openstackdocs_repo_name = 'openstack/python-glanceclient' 42 | openstackdocs_bug_project = 'python-glanceclient' 43 | openstackdocs_bug_tag = '' 44 | 45 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 46 | # text edit cycles. 47 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 48 | 49 | # TODO: remove this once no dependency uses six anymore 50 | autodoc_mock_imports = ['six'] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix of source filenames. 56 | source_suffix = '.rst' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # General information about the project. 62 | project = 'python-glanceclient' 63 | copyright = 'OpenStack Foundation' 64 | 65 | # If true, '()' will be appended to :func: etc. cross-reference text. 66 | add_function_parentheses = True 67 | 68 | # If true, the current module name will be prepended to all description 69 | # unit titles (such as .. function::). 70 | add_module_names = True 71 | 72 | # The name of the Pygments (syntax highlighting) style to use. 73 | pygments_style = 'native' 74 | 75 | # -- Options for HTML output -------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. Major themes that come with 78 | # Sphinx are currently 'default' and 'sphinxdoc'. 79 | #html_theme = 'nature' 80 | html_theme = 'openstackdocs' 81 | 82 | # Output file base name for HTML help builder. 83 | htmlhelp_basename = '%sdoc' % project 84 | 85 | 86 | # -- Options for man page output ---------------------------------------------- 87 | 88 | # Grouping the document tree for man pages. 89 | # List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' 90 | man_pages = [ 91 | ('cli/glance', 'glance', 'Client for OpenStack Images API', 92 | ['OpenStack Foundation'], 1), 93 | ] 94 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/privatekey.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/v1/test_readonly_glance.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import re 14 | 15 | from tempest.lib import exceptions 16 | 17 | from glanceclient.tests.functional import base 18 | 19 | 20 | class SimpleReadOnlyGlanceClientTest(base.ClientTestBase): 21 | 22 | """Read only functional python-glanceclient tests. 23 | 24 | This only exercises client commands that are read only. 25 | """ 26 | 27 | def test_list_v1(self): 28 | out = self.glance('--os-image-api-version 1 image-list') 29 | endpoints = self.parser.listing(out) 30 | self.assertTableStruct(endpoints, [ 31 | 'ID', 'Name', 'Disk Format', 'Container Format', 32 | 'Size', 'Status']) 33 | 34 | def test_fake_action(self): 35 | self.assertRaises(exceptions.CommandFailed, 36 | self.glance, 37 | 'this-does-not-exist') 38 | 39 | def test_member_list_v1(self): 40 | tenant_name = '--tenant-id %s' % self.creds['project_name'] 41 | out = self.glance('--os-image-api-version 1 member-list', 42 | params=tenant_name) 43 | endpoints = self.parser.listing(out) 44 | self.assertTableStruct(endpoints, 45 | ['Image ID', 'Member ID', 'Can Share']) 46 | 47 | def test_help(self): 48 | help_text = self.glance('--os-image-api-version 1 help') 49 | lines = help_text.split('\n') 50 | self.assertFirstLineStartsWith(lines, 'usage: glance') 51 | 52 | commands = [] 53 | cmds_start = lines.index('Positional arguments:') 54 | try: 55 | # Starting in Python 3.10, argparse displays options in the 56 | # "Options:" section... 57 | cmds_end = lines.index('Options:') 58 | except ValueError: 59 | # ... but before Python 3.10, options were displayed in the 60 | # "Optional arguments:" section. 61 | cmds_end = lines.index('Optional arguments:') 62 | command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') 63 | for line in lines[cmds_start:cmds_end]: 64 | match = command_pattern.match(line) 65 | if match: 66 | commands.append(match.group(1)) 67 | commands = set(commands) 68 | wanted_commands = {'bash-completion', 'help', 69 | 'image-create', 'image-delete', 70 | 'image-download', 'image-list', 71 | 'image-show', 'image-update', 72 | 'member-create', 'member-delete', 73 | 'member-list'} 74 | self.assertEqual(commands, wanted_commands) 75 | 76 | def test_version(self): 77 | self.glance('', flags='--version') 78 | 79 | def test_debug_list(self): 80 | self.glance('--os-image-api-version 1 image-list', flags='--debug') 81 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/var/wildcard-certificate.crt: -------------------------------------------------------------------------------- 1 | #Certificate: 2 | # Data: 3 | # Version: 1 (0x0) 4 | # Serial Number: 13493453254446411258 (0xbb42603e589dedfa) 5 | # Signature Algorithm: sha1WithRSAEncryption 6 | # Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com 7 | # Validity 8 | # Not Before: Aug 21 17:29:18 2013 GMT 9 | # Not After : Jul 28 17:29:18 2113 GMT 10 | # Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com 11 | # Subject Public Key Info: 12 | # Public Key Algorithm: rsaEncryption 13 | # Public-Key: (4096 bit) 14 | # Modulus: 15 | # 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: 16 | # 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: 17 | # 46:1e:85:a3:2a:9c:a4:e0:c2:14:34:4f:91:df:dc: 18 | # . 19 | # . 20 | # . 21 | # Exponent: 65537 (0x10001) 22 | # Signature Algorithm: sha1WithRSAEncryption 23 | # 9f:cc:08:5d:19:ee:54:31:a3:57:d7:3c:89:89:c0:69:41:dd: 24 | # 46:f8:73:68:ec:46:b9:fa:f5:df:f6:d9:58:35:d8:53:94:88: 25 | # bd:36:a6:23:9e:0c:0d:89:62:35:91:49:b6:14:f4:43:69:3c: 26 | # . 27 | # . 28 | # . 29 | -----BEGIN CERTIFICATE----- 30 | MIIFyjCCA7ICCQC7QmA+WJ3t+jANBgkqhkiG9w0BAQUFADCBpTELMAkGA1UEBhMC 31 | VVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQHDAZTdGF0ZTExGzAZBgNVBAoMEk9wZW5z 32 | dGFjayBUZXN0IE9yZzEcMBoGA1UECwwTT3BlbnN0YWNrIFRlc3QgVW5pdDEbMBkG 33 | A1UEAwwSKi5wb25nLmV4YW1wbGUuY29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBl 34 | eGFtcGxlLmNvbTAgFw0xMzA4MjExNzI5MThaGA8yMTEzMDcyODE3MjkxOFowgaUx 35 | CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEPMA0GA1UEBwwGU3RhdGUxMRswGQYD 36 | VQQKDBJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsME09wZW5zdGFjayBUZXN0 37 | IFVuaXQxGzAZBgNVBAMMEioucG9uZy5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJ 38 | ARYRYWRtaW5AZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK 39 | AoICAQDUuzrEoAZUMSNdsHhavkVErqGJhhHYyqgzsE/z4UYehaMqnKTgwhQ0T5Hf 40 | 3GmlIBt4I96/3cxj0qSLrdR81fM+5Km8lIlVHwVn1y6LKcMlaUC4K+sgDLcjhZfb 41 | f9+fMkcur3WlNzKpAEaIosWwsu6YvYc+W/nPBpKxMbOZ4fZiPMEo8Pxmw7sl/6hn 42 | lBOJj7dpZOZpHhVPZgzYNVoyfKCZiwgdxH4JEYa+EQos87+2Nwhs7bCgrTLLppCU 43 | vpobwZV5w4O0D6INpUfBmsr4IAuXeFWZa61vZYqhaVbAbTTlUzOLGh7Z2uz9gt75 44 | iSR2J0e2xntVaUIYLIAUNOO2edk8NMAuIOGr2EIyC7i2O/BTti2YjGNO7SsEClxi 45 | IFKjYahylHmNrS1Q/oMAcJppmhz+oOCmKOMmAZXYAH1A3gs/sWphJpgv/MWt6Ji2 46 | 4VpFaJ+o4bHILlqIpuvL4GLIOkmxVP639khaumgKtgNIUTKJ/V6t/J31WARfxKxl 47 | BQTTzV/Be+84YJiiddx8eunU8AorPyAJFzsDPTJpFUB4Q5BwAeDGCySgxJpUqM2M 48 | TETBycdiVToM4SWkRsOZgZxQ+AVfkkqDct2Bat2lg9epcIez8PrsohQjQbmiqUUL 49 | 2c3de4kLYzIWF8EN3P2Me/7b06jbn4c7Fly/AN6tJOG23BzhHQIDAQABMA0GCSqG 50 | SIb3DQEBBQUAA4ICAQCfzAhdGe5UMaNX1zyJicBpQd1G+HNo7Ea5+vXf9tlYNdhT 51 | lIi9NqYjngwNiWI1kUm2FPRDaTwC0kLxk5zBPzF7bcf0SwJCeDjmlUpY7YenS0DA 52 | XmIbg8FvgOlp69Ikrqz98Y4pB9H4O81WdjxNBBbHjrufAXxZYnh5rXrVsXeSJ8jN 53 | MYGWlSv4xwFGfRX53b8VwXFjGjAkH8SQGtRV2w9d0jF8OzFwBA4bKk4EplY0yBPR 54 | 2d7Y3RVrDnOVfV13F8CZxJ5fu+6QamUwIaTjpyqflE1L52KTy+vWPYR47H2u2bhD 55 | IeZRufJ8adNIOtH32EcENkusQjLrb3cTXGW00TljhFXd22GqL5d740u+GEKHtWh+ 56 | 9OKPTMZK8yK7d5EyS2agTVWmXU6HfpAKz9+AEOnVYErpnggNZjkmJ9kD185rGlSZ 57 | Vvo429hXoUAHNbd+8zda3ufJnJf5q4ZEl8+hp8xsvraUy83XLroVZRsKceldmAM8 58 | swt6n6w5gRKg4xTH7KFrd+KNptaoY3SsVrnJuaSOPenrUXbZzaI2Q35CId93+8NP 59 | mXVIWdPO1msdZNiCYInRIGycK+oifUZPtAaJdErg8rt8NSpHzYKQ0jfjAGiVHBjK 60 | s0J2TjoKB3jtlrw2DAmFWKeMGNp//1Rm6kfQCCXWftn+TA7XEJhcjyDBVciugA== 61 | -----END CERTIFICATE----- 62 | -------------------------------------------------------------------------------- /glanceclient/v1/apiclient/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | ######################################################################## 15 | # 16 | # THIS MODULE IS DEPRECATED 17 | # 18 | # Please refer to 19 | # https://etherpad.openstack.org/p/kilo-glanceclient-library-proposals for 20 | # the discussion leading to this deprecation. 21 | # 22 | # We recommend checking out the python-openstacksdk project 23 | # (https://launchpad.net/python-openstacksdk) instead. 24 | # 25 | ######################################################################## 26 | 27 | from oslo_utils import encodeutils 28 | from oslo_utils import uuidutils 29 | 30 | from glanceclient._i18n import _ 31 | from glanceclient.v1.apiclient import exceptions 32 | 33 | 34 | def find_resource(manager, name_or_id, **find_args): 35 | """Look for resource in a given manager. 36 | 37 | Used as a helper for the _find_* methods. 38 | Example: 39 | 40 | .. code-block:: python 41 | 42 | def _find_hypervisor(cs, hypervisor): 43 | #Get a hypervisor by name or ID. 44 | return cliutils.find_resource(cs.hypervisors, hypervisor) 45 | """ 46 | # first try to get entity as integer id 47 | try: 48 | return manager.get(int(name_or_id)) 49 | except (TypeError, ValueError, exceptions.NotFound): 50 | pass 51 | 52 | # now try to get entity as uuid 53 | try: 54 | tmp_id = encodeutils.safe_decode(name_or_id) 55 | 56 | if uuidutils.is_uuid_like(tmp_id): 57 | return manager.get(tmp_id) 58 | except (TypeError, ValueError, exceptions.NotFound): 59 | pass 60 | 61 | # for str id which is not uuid 62 | if getattr(manager, 'is_alphanum_id_allowed', False): 63 | try: 64 | return manager.get(name_or_id) 65 | except exceptions.NotFound: 66 | pass 67 | 68 | try: 69 | try: 70 | return manager.find(human_id=name_or_id, **find_args) 71 | except exceptions.NotFound: 72 | pass 73 | 74 | # finally try to find entity by name 75 | try: 76 | resource = getattr(manager, 'resource_class', None) 77 | name_attr = resource.NAME_ATTR if resource else 'name' 78 | kwargs = {name_attr: name_or_id} 79 | kwargs.update(find_args) 80 | return manager.find(**kwargs) 81 | except exceptions.NotFound: 82 | msg = _("No %(name)s with a name or " 83 | "ID of '%(name_or_id)s' exists.") % { 84 | "name": manager.resource_class.__name__.lower(), 85 | "name_or_id": name_or_id 86 | } 87 | raise exceptions.CommandError(msg) 88 | except exceptions.NoUniqueMatch: 89 | msg = _("Multiple %(name)s matches found for " 90 | "'%(name_or_id)s', use an ID to be more specific.") % { 91 | "name": manager.resource_class.__name__.lower(), 92 | "name_or_id": name_or_id 93 | } 94 | raise exceptions.CommandError(msg) 95 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - job: 2 | name: glanceclient-dsvm-functional 3 | parent: devstack-tox-functional 4 | description: | 5 | Devstack-based functional tests for glanceclient. 6 | 7 | These test glanceclient against Image API v2 only. 8 | required-projects: 9 | - openstack/python-glanceclient 10 | timeout: 4200 11 | vars: 12 | devstack_localrc: 13 | LIBS_FROM_GIT: python-glanceclient 14 | devstack_services: 15 | # turn on swift 16 | s-account: true 17 | s-container: true 18 | s-object: true 19 | s-proxy: true 20 | # Hardcode glanceclient path so the job can be run on glance patches 21 | zuul_work_dir: src/opendev.org/openstack/python-glanceclient 22 | irrelevant-files: 23 | - ^doc/.*$ 24 | - ^releasenotes/.*$ 25 | - ^.*\.rst$ 26 | - ^(test-|)requirements.txt$ 27 | - ^setup.cfg$ 28 | 29 | - job: 30 | name: glanceclient-tox-keystone-tips-base 31 | parent: tox 32 | abstract: true 33 | description: Abstract job for glanceclient vs. keystone 34 | required-projects: 35 | - name: openstack/keystoneauth 36 | 37 | - job: 38 | name: glanceclient-tox-py3-keystone-tips 39 | parent: glanceclient-tox-keystone-tips-base 40 | description: | 41 | glanceclient py3 unit tests vs. keystone masters 42 | vars: 43 | tox_envlist: py3 44 | 45 | - job: 46 | name: glanceclient-tox-oslo-tips-base 47 | parent: tox 48 | abstract: true 49 | description: Abstract job for glanceclient vs. oslo 50 | required-projects: 51 | - name: openstack/oslo.i18n 52 | - name: openstack/oslo.utils 53 | 54 | - job: 55 | name: glanceclient-tox-py3-oslo-tips 56 | parent: glanceclient-tox-oslo-tips-base 57 | description: | 58 | glanceclient py3 unit tests vs. oslo masters 59 | vars: 60 | tox_envlist: py3 61 | 62 | - job: 63 | name: glanceclient-dsvm-functional-py3 64 | parent: glanceclient-dsvm-functional 65 | vars: 66 | devstack_localrc: 67 | USE_PYTHON3: true 68 | 69 | - project: 70 | templates: 71 | - check-requirements 72 | - lib-forward-testing-python3 73 | - openstack-cover-jobs 74 | - openstack-python3-jobs 75 | - publish-openstack-docs-pti 76 | - release-notes-jobs-python3 77 | check: 78 | jobs: 79 | - glanceclient-dsvm-functional 80 | gate: 81 | jobs: 82 | - glanceclient-dsvm-functional 83 | periodic: 84 | jobs: 85 | # NOTE(rosmaita): we only want the "tips" jobs to be run against 86 | # master, hence the 'branches' qualifiers below. Without them, when 87 | # a stable branch is cut, the tests would be run against the stable 88 | # branch as well, which is pointless because these libraries are 89 | # frozen (more or less) in the stable branches. 90 | # 91 | # The "tips" jobs can be removed from the stable branch .zuul.yaml 92 | # files if someone is so inclined, but that would require manual 93 | # maintenance, so we do not do it by default. Another option is 94 | # to define these jobs in the openstack/project-config repo. 95 | # That would make us less agile in adjusting these tests, so we 96 | # aren't doing that either. 97 | - glanceclient-tox-py3-keystone-tips: 98 | branches: master 99 | - glanceclient-tox-py3-oslo-tips: 100 | branches: master 101 | experimental: 102 | jobs: 103 | - glanceclient-dsvm-functional-py3 104 | -------------------------------------------------------------------------------- /glanceclient/common/progressbar.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import sys 17 | 18 | 19 | class _ProgressBarBase(object): 20 | """A progress bar provider for a wrapped obect. 21 | 22 | Base abstract class used by specific class wrapper to show 23 | a progress bar when the wrapped object are consumed. 24 | 25 | :param wrapped: Object to wrap that hold data to be consumed. 26 | :param totalsize: The total size of the data in the wrapped object. 27 | 28 | :note: The progress will be displayed only if sys.stdout is a tty. 29 | """ 30 | 31 | def __init__(self, wrapped, totalsize): 32 | self._wrapped = wrapped 33 | self._totalsize = float(totalsize) 34 | self._show_progress = sys.stdout.isatty() and self._totalsize != 0 35 | self._percent = 0 36 | 37 | def _display_progress_bar(self, size_read): 38 | if self._show_progress: 39 | self._percent += size_read / self._totalsize 40 | # Output something like this: [==========> ] 49% 41 | sys.stdout.write('\r[{0:<30}] {1:.0%}'.format( 42 | '=' * int(round(self._percent * 29)) + '>', self._percent 43 | )) 44 | sys.stdout.flush() 45 | 46 | def __getattr__(self, attr): 47 | # Forward other attribute access to the wrapped object. 48 | return getattr(self._wrapped, attr) 49 | 50 | 51 | class VerboseFileWrapper(_ProgressBarBase): 52 | """A file wrapper with a progress bar. 53 | 54 | The file wrapper shows and advances a progress bar whenever the 55 | wrapped file's read method is called. 56 | """ 57 | 58 | def read(self, *args, **kwargs): 59 | data = self._wrapped.read(*args, **kwargs) 60 | if data: 61 | self._display_progress_bar(len(data)) 62 | else: 63 | if self._show_progress: 64 | # Break to a new line from the progress bar for incoming 65 | # output. 66 | sys.stdout.write('\n') 67 | return data 68 | 69 | 70 | class VerboseIteratorWrapper(_ProgressBarBase): 71 | """An iterator wrapper with a progress bar. 72 | 73 | The iterator wrapper shows and advances a progress bar whenever the 74 | wrapped data is consumed from the iterator. 75 | 76 | :note: Use only with iterator that yield strings. 77 | """ 78 | 79 | def __iter__(self): 80 | return self 81 | 82 | def next(self): 83 | try: 84 | data = next(self._wrapped) 85 | # NOTE(mouad): Assuming that data is a string b/c otherwise calling 86 | # len function will not make any sense. 87 | self._display_progress_bar(len(data)) 88 | return data 89 | except StopIteration: 90 | if self._show_progress: 91 | # Break to a new line from the progress bar for incoming 92 | # output. 93 | sys.stdout.write('\n') 94 | raise 95 | 96 | # In Python 3, __next__() has replaced next(). 97 | __next__ = next 98 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/base.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import os 14 | 15 | from keystoneauth1 import loading 16 | from keystoneauth1 import session 17 | from openstack import config as occ 18 | from tempest.lib.cli import base 19 | 20 | import glanceclient 21 | 22 | 23 | def credentials(cloud='devstack-admin'): 24 | """Retrieves credentials to run functional tests 25 | 26 | Credentials are either read via os-client-config from the environment 27 | or from a config file ('clouds.yaml'). Environment variables override 28 | those from the config file. 29 | 30 | devstack produces a clouds.yaml with two named clouds - one named 31 | 'devstack' which has user privs and one named 'devstack-admin' which 32 | has admin privs. This function will default to getting the devstack-admin 33 | cloud as that is the current expected behavior. 34 | """ 35 | 36 | return occ.OpenStackConfig().get_one(cloud=cloud) 37 | 38 | 39 | class ClientTestBase(base.ClientTestBase): 40 | """This is a first pass at a simple read only python-glanceclient test. 41 | 42 | This only exercises client commands that are read only. 43 | This should test commands: 44 | * as a regular user 45 | * as an admin user 46 | * with and without optional parameters 47 | * initially just check return codes, and later test command outputs 48 | 49 | """ 50 | 51 | def _get_clients(self): 52 | self.creds = credentials().get_auth_args() 53 | venv_name = os.environ.get('OS_TESTENV_NAME', 'functional') 54 | cli_dir = os.environ.get( 55 | 'OS_GLANCECLIENT_EXEC_DIR', 56 | os.path.join(os.path.abspath('.'), '.tox/%s/bin' % venv_name)) 57 | 58 | return base.CLIClient( 59 | username=self.creds['username'], 60 | password=self.creds['password'], 61 | tenant_name=self.creds['project_name'], 62 | user_domain_id=self.creds['user_domain_id'], 63 | project_domain_id=self.creds['project_domain_id'], 64 | uri=self.creds['auth_url'], 65 | cli_dir=cli_dir) 66 | 67 | def glance(self, *args, **kwargs): 68 | return self.clients.glance(*args, 69 | **kwargs) 70 | 71 | def glance_pyclient(self): 72 | ks_creds = dict( 73 | auth_url=self.creds["auth_url"], 74 | username=self.creds["username"], 75 | password=self.creds["password"], 76 | project_name=self.creds["project_name"], 77 | user_domain_id=self.creds["user_domain_id"], 78 | project_domain_id=self.creds["project_domain_id"]) 79 | keystoneclient = self.Keystone(**ks_creds) 80 | return self.Glance(keystoneclient) 81 | 82 | class Keystone(object): 83 | def __init__(self, **kwargs): 84 | loader = loading.get_plugin_loader("password") 85 | auth = loader.load_from_options(**kwargs) 86 | self.session = session.Session(auth=auth) 87 | 88 | class Glance(object): 89 | def __init__(self, keystone, version="2"): 90 | self.glance = glanceclient.Client( 91 | version, 92 | session=keystone.session) 93 | 94 | def find(self, image_name): 95 | for image in self.glance.images.list(): 96 | if image.name == image_name: 97 | return image 98 | return None 99 | -------------------------------------------------------------------------------- /glanceclient/v1/image_members.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from glanceclient.v1.apiclient import base 17 | 18 | 19 | class ImageMember(base.Resource): 20 | def __repr__(self): 21 | return "" % self._info 22 | 23 | @property 24 | def id(self): 25 | return self.member_id 26 | 27 | def delete(self): 28 | self.manager.delete(self) 29 | 30 | 31 | class ImageMemberManager(base.ManagerWithFind): 32 | resource_class = ImageMember 33 | 34 | def get(self, image, member_id): 35 | image_id = base.getid(image) 36 | url = '/v1/images/%s/members/%s' % (image_id, member_id) 37 | resp, body = self.client.get(url) 38 | member = body['member'] 39 | member['image_id'] = image_id 40 | return ImageMember(self, member, loaded=True) 41 | 42 | def list(self, image=None, member=None): 43 | out = [] 44 | if image and member: 45 | try: 46 | out.append(self.get(image, member)) 47 | # TODO(bcwaldon): narrow this down to 404 48 | except Exception: 49 | pass 50 | elif image: 51 | out.extend(self._list_by_image(image)) 52 | elif member: 53 | out.extend(self._list_by_member(member)) 54 | else: 55 | # TODO(bcwaldon): figure out what is appropriate to do here as we 56 | # are unable to provide the requested response 57 | pass 58 | return out 59 | 60 | def _list_by_image(self, image): 61 | image_id = base.getid(image) 62 | url = '/v1/images/%s/members' % image_id 63 | resp, body = self.client.get(url) 64 | out = [] 65 | for member in body['members']: 66 | member['image_id'] = image_id 67 | out.append(ImageMember(self, member, loaded=True)) 68 | return out 69 | 70 | def _list_by_member(self, member): 71 | member_id = base.getid(member) 72 | url = '/v1/shared-images/%s' % member_id 73 | resp, body = self.client.get(url) 74 | out = [] 75 | for member in body['shared_images']: 76 | member['member_id'] = member_id 77 | out.append(ImageMember(self, member, loaded=True)) 78 | return out 79 | 80 | def delete(self, image_id, member_id): 81 | self._delete("/v1/images/%s/members/%s" % (image_id, member_id)) 82 | 83 | def create(self, image, member_id, can_share=False): 84 | """Creates an image.""" 85 | url = '/v1/images/%s/members/%s' % (base.getid(image), member_id) 86 | body = {'member': {'can_share': can_share}} 87 | self.client.put(url, data=body) 88 | 89 | def replace(self, image, members): 90 | memberships = [] 91 | for member in members: 92 | try: 93 | obj = { 94 | 'member_id': member.member_id, 95 | 'can_share': member.can_share, 96 | } 97 | except AttributeError: 98 | obj = {'member_id': member['member_id']} 99 | if 'can_share' in member: 100 | obj['can_share'] = member['can_share'] 101 | memberships.append(obj) 102 | url = '/v1/images/%s/members' % base.getid(image) 103 | self.client.put(url, data={'memberships': memberships}) 104 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_client_requests.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 OpenStack Foundation 2 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from requests_mock.contrib import fixture as rm_fixture 18 | 19 | from glanceclient import client 20 | from glanceclient.tests.unit.v2.fixtures import image_create_fixture 21 | from glanceclient.tests.unit.v2.fixtures import image_list_fixture 22 | from glanceclient.tests.unit.v2.fixtures import image_show_fixture 23 | from glanceclient.tests.unit.v2.fixtures import schema_fixture 24 | from glanceclient.tests import utils as testutils 25 | from glanceclient.v2.image_schema import _BASE_SCHEMA 26 | 27 | 28 | DEFAULT_PAGE_SIZE = 200 29 | 30 | 31 | class ClientTestRequests(testutils.TestCase): 32 | """Client tests using the requests mock library.""" 33 | 34 | def test_list_bad_image_schema(self): 35 | # if kernel_id or ramdisk_id are not uuids, verify we can 36 | # still perform an image listing. Regression test for bug 37 | # 1477910 38 | self.requests = self.useFixture(rm_fixture.Fixture()) 39 | self.requests.get('http://example.com/v2/schemas/image', 40 | json=schema_fixture) 41 | self.requests.get('http://example.com/v2/images?' 42 | f'limit={DEFAULT_PAGE_SIZE}', 43 | json=image_list_fixture) 44 | gc = client.Client(2.2, "http://example.com/v2.1") 45 | images = gc.images.list() 46 | for image in images: 47 | pass 48 | 49 | def test_show_bad_image_schema(self): 50 | # if kernel_id or ramdisk_id are not uuids, verify we 51 | # don't fail due to schema validation 52 | self.requests = self.useFixture(rm_fixture.Fixture()) 53 | self.requests.get('http://example.com/v2/schemas/image', 54 | json=schema_fixture) 55 | self.requests.get('http://example.com/v2/images/%s' 56 | % image_show_fixture['id'], 57 | json=image_show_fixture) 58 | gc = client.Client(2.2, "http://example.com/v2.1") 59 | img = gc.images.get(image_show_fixture['id']) 60 | self.assertEqual(image_show_fixture['checksum'], img['checksum']) 61 | 62 | def test_invalid_disk_format(self): 63 | self.requests = self.useFixture(rm_fixture.Fixture()) 64 | self.requests.get('http://example.com/v2/schemas/image', 65 | json=_BASE_SCHEMA) 66 | self.requests.post('http://example.com/v2/images', 67 | json=image_create_fixture) 68 | self.requests.get('http://example.com/v2/images/%s' 69 | % image_show_fixture['id'], 70 | json=image_show_fixture) 71 | gc = client.Client(2.2, "http://example.com/v2.1") 72 | fields = {"disk_format": "qbull2"} 73 | try: 74 | gc.images.create(**fields) 75 | self.fail("Failed to raise exception when using bad disk format") 76 | except TypeError: 77 | pass 78 | 79 | def test_valid_disk_format(self): 80 | self.requests = self.useFixture(rm_fixture.Fixture()) 81 | self.requests.get('http://example.com/v2/schemas/image', 82 | json=_BASE_SCHEMA) 83 | self.requests.post('http://example.com/v2/images', 84 | json=image_create_fixture) 85 | self.requests.get('http://example.com/v2/images/%s' 86 | % image_show_fixture['id'], 87 | json=image_show_fixture) 88 | gc = client.Client(2.2, "http://example.com/v2.1") 89 | fields = {"disk_format": "vhdx"} 90 | gc.images.create(**fields) 91 | -------------------------------------------------------------------------------- /releasenotes/notes/pike-relnote-2c77b01aa8799f35.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | This was a quiet development cycle for the python-glanceclient. 4 | There were several improvements made in the testing code, and the 5 | documentation was reorganized in accord with the `new standard 6 | layout 7 | `_ 8 | for OpenStack projects. 9 | 10 | The main feature in this release is the addition of support for the 11 | new image import functionality introduced into Glance during this 12 | cycle. 13 | features: 14 | - | 15 | Client support has been added for the new image import functionality 16 | introduced into Glance in this cycle. This is a feature of the Images 17 | API version 2 only, and it is disabled by default in Glance. The following 18 | commands have been added to the command line interface: 19 | 20 | * ``import-info`` - gets information about the import configuration of 21 | the target Glance 22 | * ``image-stage`` - uploads image data to the staging area 23 | * ``image-import`` - initiates the import process for a previously 24 | created image record whose image data is currently in the staging area 25 | * ``image-create-via-import`` - this is an EXPERIMENTAL command that compresses 26 | the three-step import process of the Images API version 2 into a single call 27 | from the command line, just as the current client ``image-create`` command 28 | compresses a two-step process into one step. *It is EXPERIMENTAL because the 29 | name of the command may change or it may be removed entirely in future 30 | releases.* The intent is that as Glance image import is adopted by deployers, 31 | this command may be renamed to ``image-create`` as it behaves exactly the same 32 | from the user's point of view. It is included in this release so that the 33 | Glance team can get feedback from deployers and end users. 34 | 35 | fixes: 36 | - | 37 | The following are some highlights of the bug fixes included in this 38 | release. 39 | 40 | * Bug 1659010_: Help text inaccurate for container_format, disk_format 41 | * Bug 1570766_: Fix 'UnicodeEncodeError' for unicode values in url 42 | * Bug 1583919_: --no-ssl-compression is deprecated 43 | 44 | .. _1659010: https://code.launchpad.net/bugs/1659010 45 | .. _1570766: https://code.launchpad.net/bugs/1570766 46 | .. _1583919: https://code.launchpad.net/bugs/1583919 47 | 48 | other: 49 | - | 50 | The deprecated ``--no-ssl-compression`` option to the python-glanceclient 51 | command line interface has been removed_. The option has been inoperative_ 52 | since the Liberty release. 53 | 54 | - | 55 | An optimization_ was added in the case where an image download is requested 56 | from the command line interface without specifying either a filename 57 | destination for the data or output redirection. The optimization properly 58 | delays opening a connection to the server until *after* the CLI has 59 | verified that the user has specified a location for the downloaded data. 60 | In the pre-optimized code, if a user did not have permission to download 61 | the requested image or if the image had no data associated with it, the CLI 62 | would fail with an appropriate message when the client attempted to create 63 | the connection but before it had determined that there was no place to put 64 | the data. With this optimization, a user will not be able to "probe" the 65 | server to see whether image data is available without specifying either the 66 | ``--file`` option or command line redirection. 67 | 68 | - | 69 | The argument to the ``--profile`` option of the command line interface 70 | may now be specified_ by setting the value of the ``OS_PROFILE`` environment 71 | variable. 72 | 73 | .. _removed: https://opendev.org/openstack/python-glanceclient/commit/28c003dc1179ddb3124fd30c6f525dd341ae9213 74 | .. _inoperative: https://specs.openstack.org/openstack/glance-specs/specs/liberty/approved/remove-special-client-ssl-handling.html 75 | .. _optimization: https://opendev.org/openstack/python-glanceclient/commit/1df55dd952fe52c1c1fc2583326d017275b01ddc 76 | .. _specified: https://opendev.org/openstack/python-glanceclient/commit/c0f88d5fc0fd947319e022cfeba21bcb15635316 77 | -------------------------------------------------------------------------------- /glanceclient/v2/schemas.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import copy 17 | import json 18 | import jsonpatch 19 | import warlock.model as warlock 20 | 21 | 22 | class SchemaBasedModel(warlock.Model): 23 | """Glance specific subclass of the warlock Model. 24 | 25 | This implementation alters the function of the patch property 26 | to take into account the schema's core properties. With this version 27 | undefined properties which are core will generated 'replace' 28 | operations rather than 'add' since this is what the Glance API 29 | expects. 30 | """ 31 | 32 | def _make_custom_patch(self, new, original): 33 | if not self.get('tags'): 34 | tags_patch = [] 35 | else: 36 | tags_patch = [{"path": "/tags", 37 | "value": self.get('tags'), 38 | "op": "replace"}] 39 | 40 | patch_string = jsonpatch.make_patch(original, new).to_string() 41 | patch = json.loads(patch_string) 42 | if not patch: 43 | return json.dumps(tags_patch) 44 | else: 45 | return json.dumps(patch + tags_patch) 46 | 47 | @warlock.Model.patch.getter 48 | def patch(self): 49 | """Return a jsonpatch object representing the delta.""" 50 | original = copy.deepcopy(self.__dict__['__original__']) 51 | new = dict(self) 52 | if self.schema: 53 | for (name, prop) in self.schema['properties'].items(): 54 | if (name not in original and name in new and 55 | prop.get('is_base', True)): 56 | original[name] = None 57 | 58 | original['tags'] = None 59 | new['tags'] = None 60 | return self._make_custom_patch(new, original) 61 | 62 | 63 | class SchemaProperty(object): 64 | def __init__(self, name, **kwargs): 65 | self.name = name 66 | self.description = kwargs.get('description') 67 | self.is_base = kwargs.get('is_base', True) 68 | 69 | 70 | def translate_schema_properties(schema_properties): 71 | """Parse the properties dictionary of a schema document. 72 | 73 | :returns: list of SchemaProperty objects 74 | """ 75 | properties = [] 76 | for (name, prop) in schema_properties.items(): 77 | properties.append(SchemaProperty(name, **prop)) 78 | return properties 79 | 80 | 81 | class Schema(object): 82 | def __init__(self, raw_schema): 83 | self._raw_schema = raw_schema 84 | self.name = raw_schema['name'] 85 | raw_properties = raw_schema['properties'] 86 | self.properties = translate_schema_properties(raw_properties) 87 | 88 | def is_core_property(self, property_name): 89 | """Check if a property with a given name is known to the schema. 90 | 91 | Determines if it is either a base property or a custom one 92 | registered in schema-image.json file 93 | 94 | :param property_name: name of the property 95 | :returns: True if the property is known, False otherwise 96 | """ 97 | return self._check_property(property_name, True) 98 | 99 | def is_base_property(self, property_name): 100 | """Checks if a property with a given name is a base property. 101 | 102 | :param property_name: name of the property 103 | :returns: True if the property is base, False otherwise 104 | """ 105 | return self._check_property(property_name, False) 106 | 107 | def _check_property(self, property_name, allow_non_base): 108 | for prop in self.properties: 109 | if property_name == prop.name: 110 | return prop.is_base or allow_non_base 111 | return False 112 | 113 | def raw(self): 114 | return copy.deepcopy(self._raw_schema) 115 | 116 | 117 | class Controller(object): 118 | def __init__(self, http_client): 119 | self.http_client = http_client 120 | 121 | def get(self, schema_name): 122 | uri = '/v2/schemas/%s' % schema_name 123 | _, raw_schema = self.http_client.get(uri) 124 | return Schema(raw_schema) 125 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_members.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient.tests.unit.v2 import base 19 | from glanceclient.tests import utils 20 | from glanceclient.v2 import image_members 21 | 22 | 23 | IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1' 24 | MEMBER = '11223344-5566-7788-9911-223344556677' 25 | 26 | 27 | data_fixtures = { 28 | '/v2/images/{image}/members'.format(image=IMAGE): { 29 | 'GET': ( 30 | {}, 31 | {'members': [ 32 | { 33 | 'image_id': IMAGE, 34 | 'member_id': MEMBER, 35 | }, 36 | ]}, 37 | ), 38 | 'POST': ( 39 | {}, 40 | { 41 | 'image_id': IMAGE, 42 | 'member_id': MEMBER, 43 | 'status': 'pending' 44 | } 45 | ) 46 | }, 47 | '/v2/images/{image}/members/{mem}'.format(image=IMAGE, mem=MEMBER): { 48 | 'GET': ( 49 | {}, 50 | { 51 | 'image_id': IMAGE, 52 | 'member_id': MEMBER, 53 | 'status': 'pending', 54 | 'created_at': '2013-11-26T07:21:21Z', 55 | 'updated_at': '2013-11-26T07:21:21Z', 56 | 'schema': "/v2/schemas/member" 57 | }, 58 | ), 59 | 'DELETE': ( 60 | {}, 61 | None, 62 | ), 63 | 'PUT': ( 64 | {}, 65 | { 66 | 'image_id': IMAGE, 67 | 'member_id': MEMBER, 68 | 'status': 'accepted' 69 | } 70 | ), 71 | } 72 | } 73 | 74 | schema_fixtures = { 75 | 'member': { 76 | 'GET': ( 77 | {}, 78 | { 79 | 'name': 'member', 80 | 'properties': { 81 | 'image_id': {}, 82 | 'member_id': {} 83 | } 84 | }, 85 | ) 86 | } 87 | } 88 | 89 | 90 | class TestController(testtools.TestCase): 91 | def setUp(self): 92 | super(TestController, self).setUp() 93 | self.api = utils.FakeAPI(data_fixtures) 94 | self.schema_api = utils.FakeSchemaAPI(schema_fixtures) 95 | self.controller = base.BaseController(self.api, self.schema_api, 96 | image_members.Controller) 97 | 98 | def test_list_image_members(self): 99 | image_id = IMAGE 100 | image_members = self.controller.list(image_id) 101 | self.assertEqual(IMAGE, image_members[0].image_id) 102 | self.assertEqual(MEMBER, image_members[0].member_id) 103 | 104 | def test_get_image_members(self): 105 | image_member = self.controller.get(IMAGE, MEMBER) 106 | self.assertEqual(IMAGE, image_member.image_id) 107 | self.assertEqual(MEMBER, image_member.member_id) 108 | 109 | def test_delete_image_member(self): 110 | image_id = IMAGE 111 | member_id = MEMBER 112 | self.controller.delete(image_id, member_id) 113 | expect = [ 114 | ('DELETE', 115 | '/v2/images/{image}/members/{mem}'.format(image=IMAGE, 116 | mem=MEMBER), 117 | {}, 118 | None)] 119 | self.assertEqual(expect, self.api.calls) 120 | 121 | def test_update_image_members(self): 122 | image_id = IMAGE 123 | member_id = MEMBER 124 | status = 'accepted' 125 | image_member = self.controller.update(image_id, member_id, status) 126 | self.assertEqual(IMAGE, image_member.image_id) 127 | self.assertEqual(MEMBER, image_member.member_id) 128 | self.assertEqual(status, image_member.status) 129 | 130 | def test_create_image_members(self): 131 | image_id = IMAGE 132 | member_id = MEMBER 133 | status = 'pending' 134 | image_member = self.controller.create(image_id, member_id) 135 | self.assertEqual(IMAGE, image_member.image_id) 136 | self.assertEqual(MEMBER, image_member.member_id) 137 | self.assertEqual(status, image_member.status) 138 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v1/test_image_members.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient.tests import utils 19 | import glanceclient.v1.image_members 20 | import glanceclient.v1.images 21 | 22 | 23 | fixtures = { 24 | '/v1/images/1/members': { 25 | 'GET': ( 26 | {}, 27 | {'members': [ 28 | {'member_id': '1', 'can_share': False}, 29 | ]}, 30 | ), 31 | 'PUT': ({}, None), 32 | }, 33 | '/v1/images/1/members/1': { 34 | 'GET': ( 35 | {}, 36 | {'member': { 37 | 'member_id': '1', 38 | 'can_share': False, 39 | }}, 40 | ), 41 | 'PUT': ({}, None), 42 | 'DELETE': ({}, None), 43 | }, 44 | '/v1/shared-images/1': { 45 | 'GET': ( 46 | {}, 47 | {'shared_images': [ 48 | {'image_id': '1', 'can_share': False}, 49 | ]}, 50 | ), 51 | }, 52 | } 53 | 54 | 55 | class ImageMemberManagerTest(testtools.TestCase): 56 | 57 | def setUp(self): 58 | super(ImageMemberManagerTest, self).setUp() 59 | self.api = utils.FakeAPI(fixtures) 60 | self.mgr = glanceclient.v1.image_members.ImageMemberManager(self.api) 61 | self.image = glanceclient.v1.images.Image(self.api, {'id': '1'}, True) 62 | 63 | def test_list_by_image(self): 64 | members = self.mgr.list(image=self.image) 65 | expect = [('GET', '/v1/images/1/members', {}, None)] 66 | self.assertEqual(expect, self.api.calls) 67 | self.assertEqual(1, len(members)) 68 | self.assertEqual('1', members[0].member_id) 69 | self.assertEqual('1', members[0].image_id) 70 | self.assertEqual(False, members[0].can_share) 71 | 72 | def test_list_by_member(self): 73 | resource_class = glanceclient.v1.image_members.ImageMember 74 | member = resource_class(self.api, {'member_id': '1'}, True) 75 | self.mgr.list(member=member) 76 | expect = [('GET', '/v1/shared-images/1', {}, None)] 77 | self.assertEqual(expect, self.api.calls) 78 | 79 | def test_get(self): 80 | member = self.mgr.get(self.image, '1') 81 | expect = [('GET', '/v1/images/1/members/1', {}, None)] 82 | self.assertEqual(expect, self.api.calls) 83 | self.assertEqual('1', member.member_id) 84 | self.assertEqual('1', member.image_id) 85 | self.assertEqual(False, member.can_share) 86 | 87 | def test_delete(self): 88 | self.mgr.delete('1', '1') 89 | expect = [('DELETE', '/v1/images/1/members/1', {}, None)] 90 | self.assertEqual(expect, self.api.calls) 91 | 92 | def test_create(self): 93 | self.mgr.create(self.image, '1', can_share=True) 94 | expect_body = {'member': {'can_share': True}} 95 | expect = [('PUT', '/v1/images/1/members/1', {}, 96 | sorted(expect_body.items()))] 97 | self.assertEqual(expect, self.api.calls) 98 | 99 | def test_replace(self): 100 | body = [ 101 | {'member_id': '2', 'can_share': False}, 102 | {'member_id': '3'}, 103 | ] 104 | self.mgr.replace(self.image, body) 105 | expect = [('PUT', '/v1/images/1/members', {}, 106 | sorted({'memberships': body}.items()))] 107 | self.assertEqual(expect, self.api.calls) 108 | 109 | def test_replace_objects(self): 110 | body = [ 111 | glanceclient.v1.image_members.ImageMember( 112 | self.mgr, {'member_id': '2', 'can_share': False}, True), 113 | glanceclient.v1.image_members.ImageMember( 114 | self.mgr, {'member_id': '3', 'can_share': True}, True), 115 | ] 116 | self.mgr.replace(self.image, body) 117 | expect_body = { 118 | 'memberships': [ 119 | {'member_id': '2', 'can_share': False}, 120 | {'member_id': '3', 'can_share': True}, 121 | ], 122 | } 123 | expect = [('PUT', '/v1/images/1/members', {}, 124 | sorted(expect_body.items()))] 125 | self.assertEqual(expect, self.api.calls) 126 | -------------------------------------------------------------------------------- /glanceclient/v2/tasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 OpenStack Foundation 2 | # Copyright 2013 IBM Corp. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import urllib.parse 18 | 19 | from oslo_utils import encodeutils 20 | import warlock 21 | 22 | from glanceclient.common import utils 23 | from glanceclient.v2 import schemas 24 | 25 | DEFAULT_PAGE_SIZE = 20 26 | 27 | SORT_DIR_VALUES = ('asc', 'desc') 28 | SORT_KEY_VALUES = ('id', 'type', 'status') 29 | 30 | 31 | class Controller(object): 32 | def __init__(self, http_client, schema_client): 33 | self.http_client = http_client 34 | self.schema_client = schema_client 35 | 36 | @utils.memoized_property 37 | def model(self): 38 | schema = self.schema_client.get('task') 39 | return warlock.model_factory(schema.raw(), 40 | base_class=schemas.SchemaBasedModel) 41 | 42 | @utils.add_req_id_to_generator() 43 | def list(self, **kwargs): 44 | """Retrieve a listing of Task objects. 45 | 46 | :param page_size: Number of tasks to request in each paginated request 47 | :returns: generator over list of Tasks 48 | 49 | """ 50 | def paginate(url): 51 | resp, body = self.http_client.get(url) 52 | for task in body['tasks']: 53 | yield task, resp 54 | try: 55 | next_url = body['next'] 56 | except KeyError: 57 | return 58 | else: 59 | for task, resp in paginate(next_url): 60 | yield task, resp 61 | 62 | filters = kwargs.get('filters', {}) 63 | 64 | if not kwargs.get('page_size'): 65 | filters['limit'] = DEFAULT_PAGE_SIZE 66 | else: 67 | filters['limit'] = kwargs['page_size'] 68 | 69 | if 'marker' in kwargs: 70 | filters['marker'] = kwargs['marker'] 71 | 72 | sort_key = kwargs.get('sort_key') 73 | if sort_key is not None: 74 | if sort_key in SORT_KEY_VALUES: 75 | filters['sort_key'] = sort_key 76 | else: 77 | raise ValueError('sort_key must be one of the following: %s.' 78 | % ', '.join(SORT_KEY_VALUES)) 79 | 80 | sort_dir = kwargs.get('sort_dir') 81 | if sort_dir is not None: 82 | if sort_dir in SORT_DIR_VALUES: 83 | filters['sort_dir'] = sort_dir 84 | else: 85 | raise ValueError('sort_dir must be one of the following: %s.' 86 | % ', '.join(SORT_DIR_VALUES)) 87 | 88 | for param, value in filters.items(): 89 | if isinstance(value, str): 90 | filters[param] = encodeutils.safe_encode(value) 91 | 92 | url = '/v2/tasks?%s' % urllib.parse.urlencode(filters) 93 | for task, resp in paginate(url): 94 | # NOTE(flwang): remove 'self' for now until we have an elegant 95 | # way to pass it into the model constructor without conflict 96 | task.pop('self', None) 97 | yield self.model(**task), resp 98 | 99 | @utils.add_req_id_to_object() 100 | def get(self, task_id): 101 | """Get a task based on given task id.""" 102 | url = '/v2/tasks/%s' % task_id 103 | resp, body = self.http_client.get(url) 104 | # NOTE(flwang): remove 'self' for now until we have an elegant 105 | # way to pass it into the model constructor without conflict 106 | body.pop('self', None) 107 | return self.model(**body), resp 108 | 109 | @utils.add_req_id_to_object() 110 | def create(self, **kwargs): 111 | """Create a new task.""" 112 | url = '/v2/tasks' 113 | task = self.model() 114 | 115 | for (key, value) in kwargs.items(): 116 | try: 117 | setattr(task, key, value) 118 | except warlock.InvalidOperation as e: 119 | raise TypeError(str(e)) 120 | 121 | resp, body = self.http_client.post(url, data=task) 122 | # NOTE(flwang): remove 'self' for now until we have an elegant 123 | # way to pass it into the model constructor without conflict 124 | body.pop('self', None) 125 | return self.model(**body), resp 126 | -------------------------------------------------------------------------------- /glanceclient/tests/functional/v2/test_readonly_glance.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | import re 14 | 15 | from tempest.lib import exceptions 16 | 17 | from glanceclient.tests.functional import base 18 | 19 | 20 | class SimpleReadOnlyGlanceClientTest(base.ClientTestBase): 21 | 22 | """Read only functional python-glanceclient tests. 23 | 24 | This only exercises client commands that are read only. 25 | """ 26 | 27 | def test_list_v2(self): 28 | out = self.glance('--os-image-api-version 2 image-list') 29 | endpoints = self.parser.listing(out) 30 | self.assertTableStruct(endpoints, ['ID', 'Name']) 31 | 32 | def test_fake_action(self): 33 | self.assertRaises(exceptions.CommandFailed, 34 | self.glance, 35 | 'this-does-not-exist') 36 | 37 | def test_member_list_v2(self): 38 | try: 39 | # NOTE(flwang): If set disk-format and container-format, Jenkins 40 | # will raise an error said can't recognize the params, though it 41 | # works fine at local. Without the two params, Glance will 42 | # complain. So we just catch the exception can skip it. 43 | self.glance('--os-image-api-version 2 image-create --name temp') 44 | except Exception: 45 | pass 46 | out = self.glance('--os-image-api-version 2 image-list' 47 | ' --visibility private') 48 | image_list = self.parser.listing(out) 49 | # NOTE(flwang): Because the member-list command of v2 is using 50 | # image-id as required parameter, so we have to get a valid image id 51 | # based on current environment. If there is no valid image id, we will 52 | # pass in a fake one and expect a 404 error. 53 | if len(image_list) > 0: 54 | param_image_id = '--image-id %s' % image_list[0]['ID'] 55 | out = self.glance('--os-image-api-version 2 member-list', 56 | params=param_image_id) 57 | endpoints = self.parser.listing(out) 58 | self.assertTableStruct(endpoints, 59 | ['Image ID', 'Member ID', 'Status']) 60 | else: 61 | param_image_id = '--image-id fake_image_id' 62 | self.assertRaises(exceptions.CommandFailed, 63 | self.glance, 64 | '--os-image-api-version 2 member-list', 65 | params=param_image_id) 66 | 67 | def test_help(self): 68 | help_text = self.glance('--os-image-api-version 2 help') 69 | lines = help_text.split('\n') 70 | self.assertFirstLineStartsWith(lines, 'usage: glance') 71 | 72 | commands = [] 73 | cmds_start = lines.index('Positional arguments:') 74 | try: 75 | # Starting in Python 3.10, argparse displays options in the 76 | # "Options:" section... 77 | cmds_end = lines.index('Options:') 78 | except ValueError: 79 | # ... but before Python 3.10, options were displayed in the 80 | # "Optional arguments:" section. 81 | cmds_end = lines.index('Optional arguments:') 82 | command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') 83 | for line in lines[cmds_start:cmds_end]: 84 | match = command_pattern.match(line) 85 | if match: 86 | commands.append(match.group(1)) 87 | commands = set(commands) 88 | wanted_commands = {'bash-completion', 'help', 89 | 'image-create', 'image-deactivate', 'image-delete', 90 | 'image-download', 'image-list', 'image-reactivate', 91 | 'image-show', 'image-tag-delete', 92 | 'image-tag-update', 'image-update', 'image-upload', 93 | 'location-add', 'location-delete', 94 | 'location-update', 'member-create', 'member-delete', 95 | 'member-list', 'member-update', 'task-create', 96 | 'task-list', 'task-show'} 97 | self.assertFalse(wanted_commands - commands) 98 | 99 | def test_version(self): 100 | self.glance('', flags='--version') 101 | 102 | def test_debug_list(self): 103 | self.glance('--os-image-api-version 2 image-list', flags='--debug') 104 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_cache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Red Hat Inc. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | from unittest import mock 18 | 19 | from glanceclient.common import utils as common_utils 20 | from glanceclient import exc 21 | from glanceclient.tests import utils 22 | from glanceclient.v2 import cache 23 | 24 | 25 | data_fixtures = { 26 | '/v2/cache': { 27 | 'GET': ( 28 | {}, 29 | { 30 | 'cached_images': [ 31 | { 32 | 'id': 'b0aa672a-bc26-4fcb-8be1-f53ca361943d', 33 | 'Last Accessed (UTC)': '2021-08-09T07:08:20.214543', 34 | 'Last Modified (UTC)': '2021-08-09T07:08:20.214543', 35 | 'Size': 13267968, 36 | 'Hits': 0 37 | }, 38 | { 39 | 'id': 'df601a47-7251-4d20-84ae-07de335af424', 40 | 'Last Accessed (UTC)': '2021-08-09T07:08:20.214543', 41 | 'Last Modified (UTC)': '2021-08-09T07:08:20.214543', 42 | 'Size': 13267968, 43 | 'Hits': 0 44 | }, 45 | ], 46 | 'queued_images': [ 47 | '3a4560a1-e585-443e-9b39-553b46ec92d1', 48 | '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810' 49 | ], 50 | }, 51 | ), 52 | 'DELETE': ( 53 | {}, 54 | '', 55 | ), 56 | }, 57 | '/v2/cache/3a4560a1-e585-443e-9b39-553b46ec92d1': { 58 | 'PUT': ( 59 | {}, 60 | '', 61 | ), 62 | 'DELETE': ( 63 | {}, 64 | '', 65 | ), 66 | }, 67 | } 68 | 69 | 70 | class TestCacheController(testtools.TestCase): 71 | def setUp(self): 72 | super(TestCacheController, self).setUp() 73 | self.api = utils.FakeAPI(data_fixtures) 74 | self.controller = cache.Controller(self.api) 75 | 76 | @mock.patch.object(common_utils, 'has_version') 77 | def test_list_cached(self, mock_has_version): 78 | mock_has_version.return_value = True 79 | images = self.controller.list() 80 | # Verify that we have 2 cached and 2 queued images 81 | self.assertEqual(2, len(images['cached_images'])) 82 | self.assertEqual(2, len(images['queued_images'])) 83 | 84 | @mock.patch.object(common_utils, 'has_version') 85 | def test_list_cached_empty_response(self, mock_has_version): 86 | dummy_fixtures = { 87 | '/v2/cache': { 88 | 'GET': ( 89 | {}, 90 | { 91 | 'cached_images': [], 92 | 'queued_images': [], 93 | }, 94 | ), 95 | } 96 | } 97 | dummy_api = utils.FakeAPI(dummy_fixtures) 98 | dummy_controller = cache.Controller(dummy_api) 99 | mock_has_version.return_value = True 100 | images = dummy_controller.list() 101 | # Verify that we have 0 cached and 0 queued images 102 | self.assertEqual(0, len(images['cached_images'])) 103 | self.assertEqual(0, len(images['queued_images'])) 104 | 105 | @mock.patch.object(common_utils, 'has_version') 106 | def test_queue_image(self, mock_has_version): 107 | mock_has_version.return_value = True 108 | image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' 109 | self.controller.queue(image_id) 110 | expect = [('PUT', '/v2/cache/%s' % image_id, 111 | {}, None)] 112 | self.assertEqual(expect, self.api.calls) 113 | 114 | @mock.patch.object(common_utils, 'has_version') 115 | def test_cache_clear_with_header(self, mock_has_version): 116 | mock_has_version.return_value = True 117 | self.controller.clear("cache") 118 | expect = [('DELETE', '/v2/cache', 119 | {'x-image-cache-clear-target': 'cache'}, None)] 120 | self.assertEqual(expect, self.api.calls) 121 | 122 | @mock.patch.object(common_utils, 'has_version') 123 | def test_cache_delete(self, mock_has_version): 124 | mock_has_version.return_value = True 125 | image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' 126 | self.controller.delete(image_id) 127 | expect = [('DELETE', '/v2/cache/%s' % image_id, 128 | {}, None)] 129 | self.assertEqual(expect, self.api.calls) 130 | 131 | @mock.patch.object(common_utils, 'has_version') 132 | def test_cache_not_supported(self, mock_has_version): 133 | mock_has_version.return_value = False 134 | self.assertRaises(exc.HTTPNotImplemented, 135 | self.controller.list) 136 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 NTT DATA 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import testtools 16 | 17 | 18 | class BaseController(testtools.TestCase): 19 | def __init__(self, api, schema_api, controller_class): 20 | self.controller = controller_class(api, schema_api) 21 | 22 | def _assertRequestId(self, obj): 23 | self.assertIsNotNone(getattr(obj, 'request_ids', None)) 24 | self.assertEqual(['req-1234'], obj.request_ids) 25 | 26 | def list(self, *args, **kwargs): 27 | gen_obj = self.controller.list(*args, **kwargs) 28 | # For generator cases the request_ids property will be an empty list 29 | # until the underlying generator is invoked at-least once. 30 | resources = list(gen_obj) 31 | if len(resources) > 0: 32 | self._assertRequestId(gen_obj) 33 | else: 34 | # If list is empty that means geneator object has raised 35 | # StopIteration for first iteration and will not contain the 36 | # request_id in it. 37 | self.assertEqual([], gen_obj.request_ids) 38 | 39 | return resources 40 | 41 | def get_associated_image_tasks(self, *args, **kwargs): 42 | resource = self.controller.get_associated_image_tasks( 43 | *args, **kwargs) 44 | 45 | self._assertRequestId(resource) 46 | return resource 47 | 48 | def get(self, *args, **kwargs): 49 | resource = self.controller.get(*args, **kwargs) 50 | 51 | self._assertRequestId(resource) 52 | return resource 53 | 54 | def create(self, *args, **kwargs): 55 | resource = self.controller.create(*args, **kwargs) 56 | self._assertRequestId(resource) 57 | return resource 58 | 59 | def create_multiple(self, *args, **kwargs): 60 | tags = self.controller.create_multiple(*args, **kwargs) 61 | actual = [tag.name for tag in tags] 62 | self._assertRequestId(tags) 63 | return actual 64 | 65 | def update(self, *args, **properties): 66 | resource = self.controller.update(*args, **properties) 67 | self._assertRequestId(resource) 68 | return resource 69 | 70 | def delete(self, *args): 71 | resp = self.controller.delete(*args) 72 | self._assertRequestId(resp) 73 | 74 | def delete_all(self, *args): 75 | resp = self.controller.delete_all(*args) 76 | self._assertRequestId(resp) 77 | 78 | def deactivate(self, *args): 79 | resp = self.controller.deactivate(*args) 80 | self._assertRequestId(resp) 81 | 82 | def reactivate(self, *args): 83 | resp = self.controller.reactivate(*args) 84 | self._assertRequestId(resp) 85 | 86 | def upload(self, *args, **kwargs): 87 | resp = self.controller.upload(*args, **kwargs) 88 | self._assertRequestId(resp) 89 | 90 | def stage(self, *args, **kwargs): 91 | resp = self.controller.stage(*args, **kwargs) 92 | self._assertRequestId(resp) 93 | 94 | def data(self, *args, **kwargs): 95 | body = self.controller.data(*args, **kwargs) 96 | self._assertRequestId(body) 97 | return body 98 | 99 | def delete_locations(self, *args): 100 | resp = self.controller.delete_locations(*args) 101 | self._assertRequestId(resp) 102 | 103 | def add_location(self, *args, **kwargs): 104 | resp = self.controller.add_location(*args, **kwargs) 105 | self._assertRequestId(resp) 106 | 107 | def update_location(self, *args, **kwargs): 108 | resp = self.controller.update_location(*args, **kwargs) 109 | self._assertRequestId(resp) 110 | 111 | def associate(self, *args, **kwargs): 112 | resource_types = self.controller.associate(*args, **kwargs) 113 | self._assertRequestId(resource_types) 114 | return resource_types 115 | 116 | def deassociate(self, *args): 117 | resp = self.controller.deassociate(*args) 118 | self._assertRequestId(resp) 119 | 120 | def image_import(self, *args, **kwargs): 121 | resp = self.controller.image_import(*args, **kwargs) 122 | self._assertRequestId(resp) 123 | 124 | def add_image_location(self, *args): 125 | resp = self.controller.add_image_location(*args) 126 | self._assertRequestId(resp) 127 | 128 | def get_image_locations(self, *args): 129 | resource = self.controller.get_image_locations(*args) 130 | self._assertRequestId(resource) 131 | return resource 132 | 133 | 134 | class BaseResourceTypeController(BaseController): 135 | def __init__(self, api, schema_api, controller_class): 136 | super(BaseResourceTypeController, self).__init__(api, schema_api, 137 | controller_class) 138 | 139 | def get(self, *args, **kwargs): 140 | resource_types = self.controller.get(*args) 141 | names = [rt.name for rt in resource_types] 142 | self._assertRequestId(resource_types) 143 | return names 144 | -------------------------------------------------------------------------------- /glanceclient/exc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import re 17 | import sys 18 | 19 | 20 | class BaseException(Exception): 21 | """An error occurred.""" 22 | def __init__(self, message=None): 23 | self.message = message 24 | 25 | def __str__(self): 26 | return self.message or self.__class__.__doc__ 27 | 28 | 29 | class CommandError(BaseException): 30 | """Invalid usage of CLI.""" 31 | 32 | 33 | class InvalidEndpoint(BaseException): 34 | """The provided endpoint is invalid.""" 35 | 36 | 37 | class CommunicationError(BaseException): 38 | """Unable to communicate with server.""" 39 | 40 | 41 | class ClientException(Exception): 42 | """DEPRECATED!""" 43 | 44 | 45 | class HTTPException(ClientException): 46 | """Base exception for all HTTP-derived exceptions.""" 47 | code = 'N/A' 48 | 49 | def __init__(self, details=None): 50 | self.details = details or self.__class__.__name__ 51 | 52 | def __str__(self): 53 | return "HTTP %s" % (self.details) 54 | 55 | 56 | class HTTPMultipleChoices(HTTPException): 57 | code = 300 58 | 59 | def __str__(self): 60 | self.details = ("Requested version of OpenStack Images API is not " 61 | "available.") 62 | return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, 63 | self.details) 64 | 65 | 66 | class BadRequest(HTTPException): 67 | """DEPRECATED!""" 68 | code = 400 69 | 70 | 71 | class HTTPBadRequest(BadRequest): 72 | pass 73 | 74 | 75 | class Unauthorized(HTTPException): 76 | """DEPRECATED!""" 77 | code = 401 78 | 79 | 80 | class HTTPUnauthorized(Unauthorized): 81 | pass 82 | 83 | 84 | class Forbidden(HTTPException): 85 | """DEPRECATED!""" 86 | code = 403 87 | 88 | 89 | class HTTPForbidden(Forbidden): 90 | pass 91 | 92 | 93 | class NotFound(HTTPException): 94 | """DEPRECATED!""" 95 | code = 404 96 | 97 | 98 | class HTTPNotFound(NotFound): 99 | pass 100 | 101 | 102 | class HTTPMethodNotAllowed(HTTPException): 103 | code = 405 104 | 105 | 106 | class Conflict(HTTPException): 107 | """DEPRECATED!""" 108 | code = 409 109 | 110 | 111 | class HTTPConflict(Conflict): 112 | pass 113 | 114 | 115 | class OverLimit(HTTPException): 116 | """DEPRECATED!""" 117 | code = 413 118 | 119 | 120 | class HTTPOverLimit(OverLimit): 121 | pass 122 | 123 | 124 | class HTTPInternalServerError(HTTPException): 125 | code = 500 126 | 127 | 128 | class HTTPNotImplemented(HTTPException): 129 | code = 501 130 | 131 | 132 | class HTTPBadGateway(HTTPException): 133 | code = 502 134 | 135 | 136 | class ServiceUnavailable(HTTPException): 137 | """DEPRECATED!""" 138 | code = 503 139 | 140 | 141 | class HTTPServiceUnavailable(ServiceUnavailable): 142 | pass 143 | 144 | 145 | # NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception 146 | # classes 147 | _code_map = {} 148 | for obj_name in dir(sys.modules[__name__]): 149 | if obj_name.startswith('HTTP'): 150 | obj = getattr(sys.modules[__name__], obj_name) 151 | _code_map[obj.code] = obj 152 | 153 | 154 | def from_response(response, body=None): 155 | """Return an instance of an HTTPException based on httplib response.""" 156 | cls = _code_map.get(response.status_code, HTTPException) 157 | if body and 'json' in response.headers['content-type']: 158 | # Iterate over the nested objects and retrieve the "message" attribute. 159 | messages = [obj.get('message') for obj in response.json().values()] 160 | # Join all of the messages together nicely and filter out any objects 161 | # that don't have a "message" attr. 162 | details = '\n'.join(i for i in messages if i is not None) 163 | return cls(details=details) 164 | elif body and 'html' in response.headers['content-type']: 165 | # Split the lines, strip whitespace and inline HTML from the response. 166 | details = [re.sub(r'<.+?>', '', i.strip()) 167 | for i in response.text.splitlines()] 168 | details = [i for i in details if i] 169 | # Remove duplicates from the list. 170 | details_seen = set() 171 | details_temp = [] 172 | for i in details: 173 | if i not in details_seen: 174 | details_temp.append(i) 175 | details_seen.add(i) 176 | # Return joined string separated by colons. 177 | details = ': '.join(details_temp) 178 | return cls(details=details) 179 | elif body: 180 | body = body.decode('utf-8') 181 | details = body.replace('\n\n', '\n') 182 | return cls(details=details) 183 | 184 | return cls() 185 | 186 | 187 | class NoTokenLookupException(Exception): 188 | """DEPRECATED!""" 189 | pass 190 | 191 | 192 | class EndpointNotFound(Exception): 193 | """DEPRECATED!""" 194 | pass 195 | 196 | 197 | class SSLConfigurationError(BaseException): 198 | pass 199 | 200 | 201 | class SSLCertificateError(BaseException): 202 | pass 203 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_metadefs_tags.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 OpenStack Foundation. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient.tests.unit.v2 import base 19 | from glanceclient.tests import utils 20 | from glanceclient.v2 import metadefs 21 | 22 | NAMESPACE1 = 'Namespace1' 23 | TAG1 = 'Tag1' 24 | TAG2 = 'Tag2' 25 | TAGNEW1 = 'TagNew1' 26 | TAGNEW2 = 'TagNew2' 27 | TAGNEW3 = 'TagNew3' 28 | 29 | 30 | def _get_tag_fixture(tag_name, **kwargs): 31 | tag = { 32 | "name": tag_name 33 | } 34 | tag.update(kwargs) 35 | return tag 36 | 37 | 38 | data_fixtures = { 39 | "/v2/metadefs/namespaces/%s/tags" % NAMESPACE1: { 40 | "GET": ( 41 | {}, 42 | { 43 | "tags": [ 44 | _get_tag_fixture(TAG1), 45 | _get_tag_fixture(TAG2) 46 | ] 47 | } 48 | ), 49 | "POST": ( 50 | {}, 51 | { 52 | 'tags': [ 53 | _get_tag_fixture(TAGNEW2), 54 | _get_tag_fixture(TAGNEW3) 55 | ] 56 | } 57 | ), 58 | "DELETE": ( 59 | {}, 60 | {} 61 | ) 62 | }, 63 | "/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW1): { 64 | "POST": ( 65 | {}, 66 | _get_tag_fixture(TAGNEW1) 67 | ) 68 | }, 69 | "/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAG1): { 70 | "GET": ( 71 | {}, 72 | _get_tag_fixture(TAG1) 73 | ), 74 | "PUT": ( 75 | {}, 76 | _get_tag_fixture(TAG2) 77 | ), 78 | "DELETE": ( 79 | {}, 80 | {} 81 | ) 82 | }, 83 | "/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAG2): { 84 | "GET": ( 85 | {}, 86 | _get_tag_fixture(TAG2) 87 | ), 88 | }, 89 | "/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW2): { 90 | "GET": ( 91 | {}, 92 | _get_tag_fixture(TAGNEW2) 93 | ), 94 | }, 95 | "/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW3): { 96 | "GET": ( 97 | {}, 98 | _get_tag_fixture(TAGNEW3) 99 | ), 100 | } 101 | 102 | } 103 | 104 | schema_fixtures = { 105 | "metadefs/tag": { 106 | "GET": ( 107 | {}, 108 | { 109 | "additionalProperties": True, 110 | "name": { 111 | "type": "string" 112 | }, 113 | "created_at": { 114 | "type": "string", 115 | "readOnly": True, 116 | "description": ("Date and time of tag creation"), 117 | "format": "date-time" 118 | }, 119 | "updated_at": { 120 | "type": "string", 121 | "readOnly": True, 122 | "description": ("Date and time of the last tag" 123 | " modification"), 124 | "format": "date-time" 125 | }, 126 | 'properties': {} 127 | } 128 | ) 129 | } 130 | } 131 | 132 | 133 | class TestTagController(testtools.TestCase): 134 | def setUp(self): 135 | super(TestTagController, self).setUp() 136 | self.api = utils.FakeAPI(data_fixtures) 137 | self.schema_api = utils.FakeSchemaAPI(schema_fixtures) 138 | self.controller = base.BaseController(self.api, self.schema_api, 139 | metadefs.TagController) 140 | 141 | def test_list_tag(self): 142 | tags = self.controller.list(NAMESPACE1) 143 | actual = [tag.name for tag in tags] 144 | self.assertEqual([TAG1, TAG2], actual) 145 | 146 | def test_get_tag(self): 147 | tag = self.controller.get(NAMESPACE1, TAG1) 148 | self.assertEqual(TAG1, tag.name) 149 | 150 | def test_create_tag(self): 151 | tag = self.controller.create(NAMESPACE1, TAGNEW1) 152 | self.assertEqual(TAGNEW1, tag.name) 153 | 154 | def test_create_multiple_tags(self): 155 | properties = { 156 | 'tags': [TAGNEW2, TAGNEW3] 157 | } 158 | tags = self.controller.create_multiple(NAMESPACE1, **properties) 159 | self.assertEqual([TAGNEW2, TAGNEW3], tags) 160 | 161 | def test_update_tag(self): 162 | properties = { 163 | 'name': TAG2 164 | } 165 | tag = self.controller.update(NAMESPACE1, TAG1, **properties) 166 | self.assertEqual(TAG2, tag.name) 167 | 168 | def test_delete_tag(self): 169 | self.controller.delete(NAMESPACE1, TAG1) 170 | expect = [ 171 | ('DELETE', 172 | '/v2/metadefs/namespaces/%s/tags/%s' % (NAMESPACE1, TAG1), 173 | {}, 174 | None)] 175 | self.assertEqual(expect, self.api.calls) 176 | 177 | def test_delete_all_tags(self): 178 | self.controller.delete_all(NAMESPACE1) 179 | expect = [ 180 | ('DELETE', 181 | '/v2/metadefs/namespaces/%s/tags' % NAMESPACE1, 182 | {}, 183 | None)] 184 | self.assertEqual(expect, self.api.calls) 185 | -------------------------------------------------------------------------------- /glanceclient/tests/unit/v2/test_metadefs_resource_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 OpenStack Foundation. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import testtools 17 | 18 | from glanceclient.tests.unit.v2 import base 19 | from glanceclient.tests import utils 20 | from glanceclient.v2 import metadefs 21 | 22 | NAMESPACE1 = 'Namespace1' 23 | RESOURCE_TYPE1 = 'ResourceType1' 24 | RESOURCE_TYPE2 = 'ResourceType2' 25 | RESOURCE_TYPE3 = 'ResourceType3' 26 | RESOURCE_TYPE4 = 'ResourceType4' 27 | RESOURCE_TYPENEW = 'ResourceTypeNew' 28 | 29 | 30 | data_fixtures = { 31 | "/v2/metadefs/namespaces/%s/resource_types" % NAMESPACE1: { 32 | "GET": ( 33 | {}, 34 | { 35 | "resource_type_associations": [ 36 | { 37 | "name": RESOURCE_TYPE3, 38 | "created_at": "2014-08-14T09:07:06Z", 39 | "updated_at": "2014-08-14T09:07:06Z", 40 | }, 41 | { 42 | "name": RESOURCE_TYPE4, 43 | "prefix": "PREFIX:", 44 | "created_at": "2014-08-14T09:07:06Z", 45 | "updated_at": "2014-08-14T09:07:06Z", 46 | } 47 | ] 48 | } 49 | ), 50 | "POST": ( 51 | {}, 52 | { 53 | "name": RESOURCE_TYPENEW, 54 | "prefix": "PREFIX:", 55 | "created_at": "2014-08-14T09:07:06Z", 56 | "updated_at": "2014-08-14T09:07:06Z", 57 | } 58 | ), 59 | }, 60 | "/v2/metadefs/namespaces/%s/resource_types/%s" % (NAMESPACE1, 61 | RESOURCE_TYPE1): 62 | { 63 | "DELETE": ( 64 | {}, 65 | {} 66 | ), 67 | }, 68 | "/v2/metadefs/resource_types": { 69 | "GET": ( 70 | {}, 71 | { 72 | "resource_types": [ 73 | { 74 | "name": RESOURCE_TYPE1, 75 | "created_at": "2014-08-14T09:07:06Z", 76 | "updated_at": "2014-08-14T09:07:06Z", 77 | }, 78 | { 79 | "name": RESOURCE_TYPE2, 80 | "created_at": "2014-08-14T09:07:06Z", 81 | "updated_at": "2014-08-14T09:07:06Z", 82 | } 83 | ] 84 | } 85 | ) 86 | } 87 | } 88 | 89 | schema_fixtures = { 90 | "metadefs/resource_type": { 91 | "GET": ( 92 | {}, 93 | { 94 | "name": "resource_type", 95 | "properties": { 96 | "prefix": { 97 | "type": "string", 98 | "description": "Specifies the prefix to use for the " 99 | "given resource type. Any properties " 100 | "in the namespace should be prefixed " 101 | "with this prefix when being applied " 102 | "to the specified resource type. Must " 103 | "include prefix separator (e.g. a " 104 | "colon :).", 105 | "maxLength": 80 106 | }, 107 | "properties_target": { 108 | "type": "string", 109 | "description": "Some resource types allow more than " 110 | "one key / value pair per instance. " 111 | "For example, Cinder allows user and " 112 | "image metadata on volumes. Only the " 113 | "image properties metadata is " 114 | "evaluated by Nova (scheduling or " 115 | "drivers). This property allows a " 116 | "namespace target to remove the " 117 | "ambiguity.", 118 | "maxLength": 80 119 | }, 120 | "name": { 121 | "type": "string", 122 | "description": "Resource type names should be " 123 | "aligned with Heat resource types " 124 | "whenever possible: http://docs." 125 | "openstack.org/developer/heat/" 126 | "template_guide/openstack.html", 127 | "maxLength": 80 128 | }, 129 | "created_at": { 130 | "type": "string", 131 | "readOnly": True, 132 | "description": "Date and time of resource type " 133 | "association", 134 | "format": "date-time" 135 | }, 136 | "updated_at": { 137 | "type": "string", 138 | "readOnly": True, 139 | "description": "Date and time of the last resource " 140 | "type association modification ", 141 | "format": "date-time" 142 | }, 143 | } 144 | } 145 | ) 146 | } 147 | } 148 | 149 | 150 | class TestResoureTypeController(testtools.TestCase): 151 | def setUp(self): 152 | super(TestResoureTypeController, self).setUp() 153 | self.api = utils.FakeAPI(data_fixtures) 154 | self.schema_api = utils.FakeSchemaAPI(schema_fixtures) 155 | self.controller = base.BaseResourceTypeController( 156 | self.api, self.schema_api, metadefs.ResourceTypeController) 157 | 158 | def test_list_resource_types(self): 159 | resource_types = self.controller.list() 160 | names = [rt.name for rt in resource_types] 161 | self.assertEqual([RESOURCE_TYPE1, RESOURCE_TYPE2], names) 162 | 163 | def test_get_resource_types(self): 164 | resource_types = self.controller.get(NAMESPACE1) 165 | self.assertEqual([RESOURCE_TYPE3, RESOURCE_TYPE4], resource_types) 166 | 167 | def test_associate_resource_types(self): 168 | resource_types = self.controller.associate(NAMESPACE1, 169 | name=RESOURCE_TYPENEW) 170 | 171 | self.assertEqual(RESOURCE_TYPENEW, resource_types['name']) 172 | 173 | def test_associate_resource_types_invalid_property(self): 174 | longer = '1234' * 50 175 | properties = {'name': RESOURCE_TYPENEW, 'prefix': longer} 176 | self.assertRaises(TypeError, self.controller.associate, NAMESPACE1, 177 | **properties) 178 | 179 | def test_deassociate_resource_types(self): 180 | self.controller.deassociate(NAMESPACE1, RESOURCE_TYPE1) 181 | expect = [ 182 | ('DELETE', 183 | '/v2/metadefs/namespaces/%s/resource_types/%s' % (NAMESPACE1, 184 | RESOURCE_TYPE1), 185 | {}, 186 | None)] 187 | self.assertEqual(expect, self.api.calls) 188 | --------------------------------------------------------------------------------