././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.3122718 python-ironicclient-4.11.0/0000775000175000017500000000000000000000000015621 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/.coveragerc0000664000175000017500000000011100000000000017733 0ustar00zuulzuul00000000000000[report] include = ironicclient/* omit = ironicclient/tests/functional/* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/.stestr.conf0000664000175000017500000000010700000000000020070 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${TESTS_DIR:-./ironicclient/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793348.0 python-ironicclient-4.11.0/AUTHORS0000664000175000017500000001551100000000000016674 0ustar00zuulzuul00000000000000Adam Gandelman Aija Jauntēva Alexey Galkin Aline Bousquet Andreas Jaeger Andreas Jaeger Andrey Kurilin Anh Tran Anton Arefiev Arne Wiebalck Arun S A G Ben Nemec Bob Fournier Brad P. Crochet Cenne ChangBo Guo(gcb) Chaozhe.Chen Charles Short ChenZheng Chris Krelle Christian Berendt Chuck Short Clark Boylan Clif Houck Corey Bryant Dao Cong Tien Davanum Srinivas David Hu David Shrewsbury Devananda van der Veen Diego de Lima Pereira Dirk Mueller Dmitry Tantsur Dmitry Tantsur Dmitry Tantsur Doug Hellmann Eric Fried Eric Fried Felix Huettner Flavio Percoco Florian Fuchs Galyna Zholtkevych Gaëtan Trellu Ghanshyam Mann Ghe Rivero Guang Yee Gábor Antal Hamdy Khader Hangdong Zhang HaoZhi, Cui Haomeng, Wang He Yongli Hervé Beraud Himanshu Kumar Hironori Shiina Ian Wienand Ilya Etingof Iury Gregory Melo Ferreira James E. Blair Jamie Lennox Jamie Lennox Jason Jeremy Stanley Jim Rollenhagen John L. Villalovos John L. Villalovos John Trowbridge JuPing Julia Kreger KATO Tomoyuki KaiFeng Wang Kaifeng Wang Kan Kevin McDonald Kui Shi Kyrylo Romanenko Lin Tan Lin Tan LiuNanke Lokesh S Lucas Alvares Gomes Luong Anh Tuan M V P Nitesh Madhuri Kumari Mahnoor Asghar Marc Aubry Mario Villaplana Mark Goddard Martin Geisler Mathieu Gagné Matt Keenan Maxime Belanger Michael Davies Michael Johnson Michael Turek Mikhail Durnosvistov Miles Gould Monty Taylor Motohiro OTSUKA Nam Nguyen Hoai Naohiro Tamura Nguyen Hai Nisha Agarwal Ondřej Nový OpenStack Release Bot Pavlo Shchelokovskyy Pavlo Shchelokovskyy Pierre Riteau Rakesh H S Ramakrishnan G Riccardo Pittau Rodion Promyshlennikov Ruby Loo Ruby Loo Ruby Loo Ruby Loo Rui Chen Sam Betts Sascha Peilicke Sean McGinnis Sergey Lupersolsky Sergey Turivnyi Sergii Turivnyi Shuquan Huang Sinval Vieira SofiiaAndriichenko Steve Baker Steve Martinelli Tadeas Kot Tang Chen Tang Chen Tao Li TienDC Tony Breeds Tuan Do Anh Tzu-Mainn Chen Ukesh Kumar Vasudevan Vadim Hmyrov Vasyl Saienko Victor Sergeyev Vladyslav Drok Vu Cong Tuan William Stevenson XinxinShen Yang Hongyang Yolanda Robla Yuiko Takada YuikoTakada Yuriy Zveryanskyy Yushiro FURUKAWA Zenghui Shi Zhenguo Niu chenke deepakmourya dnuka ericxiett fpxie ghanshyam huang.zhiping jiang wei jiangfei likui linbing linggao lingyongxu liuqing liushuobj llg8212 maaoyu max_lobur melissaml qinchunhua qingszhao ricolin sandriichenko shu-mutou sonu.kumar sunjia venkatamahesh vishal mahajan wangfaxin wu.chunyang wu.chunyang wu.shiming xiexs ya.wang zhengchuan hu zhulingjie ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/CONTRIBUTING.rst0000664000175000017500000000051700000000000020265 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html More information on contributing can be found within the project documentation: https://docs.openstack.org/python-ironicclient/latest/contributor/contributing.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793347.0 python-ironicclient-4.11.0/ChangeLog0000664000175000017500000011471400000000000017403 0ustar00zuulzuul00000000000000CHANGES ======= 4.11.0 ------ 4.10.0 ------ * Improve description of "node boot mode set" * Use only Yoga tests * Updating python testing classifier as per Yoga testing runtime * Add node history support * Test python 3.6 for distributions compatibility * Made \`baremetal --help\` display command specific help * Fix references to ironicclient classes and methods 4.9.0 ----- * Fix distribution compatability for configdrive build * Add Python3 yoga unit tests * Update master for stable/xena * Add support for fields in drivers CLI * Validate path when passing a config-drive 4.8.0 ----- * Add support for changing 'boot\_mode' and 'secure\_boot' states * Add lower-constraints job to current development branch * Include BIOS registry fields in bios setting list command * Fix for missing fonts in PDF jobs * Add support for 'boot\_mode' and 'secure\_boot' node resource fields * Changed minversion in tox to 3.18.0 * Update project conundrum related docs * Switch testing to Xena testing runtime 4.7.0 ----- * setup.cfg: Replace dashes with underscores * Make baremetal --debug actually work * Fix the functional tests * Add Python3 xena unit tests * Update master for stable/wallaby * Fix --fields network\_data 4.6.1 ----- 4.6.0 ----- * Clearer error message when unable to parse JSON * Support YAML files wherever JSON files are accepted * Add missing unit tests for provision state commands * Update minversion of tox * Add 'deploy steps' for provisioning API 4.5.0 ----- * Add PyPi link to readme.rst * Support setting automated\_clean to False * Move pep8 dependencies from test-requirements to tox.ini * remove lower-constraints job tox.ini * Remove lower-constraints job * Use TOX\_CONSTRAINTS\_FILE * Update create node from file example * Set safe version of hacking * Remove install unnecessary packages * requirements: Drop os-testr * Remove install unnecessary packages * Fix l-c job * Add Python3 wallaby unit tests * Update master for stable/victoria * Add tests for 'baremetal port create' command 4.3.0 ----- * Remove Babel requirement * Fix expected exception message in one test * Set min version of tox to 3.2.1 * Remove pypy * Add port-uuid parameter to node vif attach * Fix missing tox in functional devstack-minimal based job * Add release note regarding global\_request\_id * Allow to pass global request id for remaining objects * Allow to pass global request id in port and volume * Allow to pass global request id in node methods * Fix pygments style 4.2.0 ----- * drop mock from lower-constraints * Cap jsonschema 3.2.0 as the minimal version * Update lower-constraints.txt * Switch to newer openstackdocstheme and reno versions * Add global\_request\_id into the base client methods * Allow to pass additional arguments into adapter * Remove future dependency * Get rid of the oslo.serialization dependency * Add py38 package metadata * Use unittest.mock instead of third party mock * Restore default netboot boot option * Check import order * Add Python3 victoria unit tests * Update master for stable/ussuri * Add \`network\_data\` ironic node attribute support * Convert ironicclient-tempest to dib 4.1.0 ----- * Standalone CLI: fix a minor issue in the docs * Get rid of the oslo.config requirement * Standalone CLI: optional support for ironic-inspector * Make oslo.i18n an optional dependency * A standalone CLI for ironic * Cleanup py27 support * Add node lessee * Bump hacking to 3.0.0 * Explicitly set ramdisk type * Add --no-retired list option 4.0.0 ----- * Provide a clear error message when using client.Client without a session * Add support for retired{\_reason} fields * Remove VerifiedHTTPSConnection class * Add allocation owner * Enforce running tox with correct python version based on env * Stop using six library * Fix AttributeError in negotiate\_version * Drop python 2.7 support and testing * Switch jobs to python3 * Switch to Ussuri job * Fix source link * Add versions to release notes series * Fix usage of --owner filter during node list * Make it clear that a Session is required for v1.client.Client * Fix TypeError when using endpoint\_override with SessionClient * Make the dependency on oslo.config explicit * Remove the requirement on either endpoint\_override or os\_ironic\_api\_version * Update master for stable/train 3.0.0 ----- * Build pdf doc * Remove deprecated keystone arguments * Remove deprecated endpoint argument * Remove the ironic command * Remove deprecated common.http.HTTPClient class * Add release note for bug #2006216 * Strip prefix when paginating * Update api-ref location * Switch to the new canonical constraints URL on master * Update Python 3 test runtimes for Train 2.8.0 ----- * Add allocation update API * Update sphinx requirements * Allocation API: support allocation backfilling * Do not try to use /v1/v1 when endpoint\_override is used * Fetch requirements from opendev * OpenDev Migration Patch * Dropping the py35 testing * Uncap jsonschema * Replace openstack.org git:// URLs with https:// * Update master for stable/stein * Run jobs under python2 and python3 * Use endpoint\_override in version negotiation * Move to zuulv3 2.7.0 ----- * Update release notes * Follow-up to the configdrive change * Accept 'valid\_interfaces' in client setup * pass endpoint interface to http client * Support passing a dictionary for configdrive * Deploy templates: client support * [Trivial] Allocation API: fix incorrect parameter description * [Follow Up] Add 'hostname' to port's local link connection * Replace mock.has\_calls() with assert\_has\_calls * Add 'hostname' to port's local link connection * Add is-smartnic port attribute to port command * Support node description * Allocation API: client API and CLI * add python 3.7 unit test job * Add Events support * Add node owner * Support for conductors exposed from API * Change openstack-dev to openstack-discuss 2.6.0 ----- * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Support for protected and protected\_reason fields * Sort columns in node detailed list alphabetically * Add management of automated\_clean field * Update min tox version to 2.0 * Fix a LOG.warning which didn't work properly * Fix a typo in the docstring * Modify useful links to project resources in README * Minor fixes to README.rst * Use templates for cover and lower-constraints * Replace assertRaisesRegexp with assertRaisesRegex * fix typo * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Update reno for stable/rocky 2.5.0 ----- * Add support for conductor groups * Add support for reset\_interfaces in node's PATCH * Support resetting interfaces to their default values * Remove testrepository * Fix lower-constraints.txt * Support node's deploy\_step field * Trivial fix typo of description 2.4.0 ----- * Add microversion override for get and list * BIOS Settings support * Switch functional tests to the fake-hardware hardware type * Power fault recovery: client support * fix tox python3 overrides * Support per-call version: set\_provision\_state * Switch to using stestr * Wire in header microversion into negotiation * Include python API reference in docs * Do not abort wait\_for\_provision\_state of last\_errors becomes non-empty * Allow to use none auth in functional tests * Stop double json decoding API error messages * Gate fix: Cap hacking to avoid gate failure * Switch to none auth for standalone mode * Do not run functional (API) tests in the CI * Follow the new PTI for document build * Fix incompatible requirement in lower-constraints * Update references to launchpad for storyboard * Updated from global requirements * fix a typo in documentation * add lower-constraints job * Minor version bump for 2.3.0 * Updated from global requirements * [doc] Add 'openstack create' command to command reference * Switch the CI to hardware types and clean up playbook * Updated from global requirements * Minor changes to version negotiation logic * Updated from global requirements 2.3.0 ----- * Updated from global requirements * Change confusing test class names * Update 'Usage' section in 'doc/source/api\_v1.rst' * Updated from global requirements * Replace use of functools.wraps() with six.wraps() * Zuul: Remove project name * Use 'with' method rather than having to call close * Follow-up of rescue mode * Add rescue\_interface to node and driver * Add support for RESCUE and UNRESCUE provision states * Update reno for stable/queens 2.2.0 ----- * Add release note for fix to bug 1745099 * Replace curly quotes with straight quotes * Use the 'ironic' queue for the gate * Check return value in test\_baremetal\_list\_traits * Traits support * Can not set portgroup mode as a number * Updated from global requirements * Allow API user to define list of versions * Facilitate latest Rest API use * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use StrictVersion to compare versions * Accept port and portgroup as volume connector types * Ignore .eggs from git 2.1.0 ----- * Updated from global requirements * Accept None as a result of node validation in functional test * Use the tempest plugin from openstack/ironic-tempest-plugin * Remove RBD examples * Use assertRegex instead of assertRegexpMatches * Updated from global requirements * Avoid tox\_install.sh for constraints support * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * osc node power on & off commands * zuul: centralize 'irrelevant-files' list * Updated from global requirements 2.0.0 ----- * Update release notes * [reno] Prelude for release 2.0 * Move legacy ironicclient jobs in-tree * Mock filecache.CACHE in unit tests * Add ability to provide configdrive when rebuilding with OSC * Pass missing arguments to session in SessionClient.\_make\_session\_request * Switch the deprecated "ironic" CLI to "latest" API version by default * Make functional tests on JSON output debugable * Set the default API version of OSC CLI to "latest" * Synchronize ironic and ironicclients list of boot devices * Allow re-negotiation of the latest version supplied by CLI * Clean up the release notes * Use generic user for both zuul v2 and v3 * Deprecate the ironic CLI * Do not use urljoin in base http client * Replace testr with stestr * Update documentation * Update README * Updated from global requirements * Cleanup test-requirements * Do not depend on python-openstackclient * Updated from global requirements * Updated from global requirements * Remove deprecated OSC baremetal commands * flake8: Enable some off-by-default checks * Updated from global requirements * Fix to use "." to source script files * tox.ini: Add 'py36' to the default envlist * Allow OS\_BAREMETAL\_API\_VERSION=latest to work * Add auto-generated CLI reference * Updated from global requirements * Add test for set/unset node target\_raid\_config * Skip warning when changing target\_raid\_config * Update the documentation link for doc migration * Updated from global requirements * Add basic tests for OSC plugin baremetal driver commands * Pass os\_identity\_api\_version into functional tests * Update reno for stable/pike 1.16.0 ------ * Turn on warning-is-error * Follow up to the API version warning patches * Updated from global requirements * Log warning when API version is not specified for the ironic tool 1.15.0 ------ * Update volume release notes to fix reno * Add physical network to port commands * Update and optimize documentation links * Updated from global requirements * Follow up for OSC volume target commands * Add Ironic CLI commands for volume target * Rearrange existing documentation to fit the new standard layout * Updated from global requirements * Follow-up release note revision * Add Ironic CLI commands for volume connector * Add support for storage\_interface to node and driver CLI * Add OSC commands for volume target * Follow up for OSC volume connector commands * Remove useless variables assignment in unit test * Fixed wrap from taking negative values * Replace http with https * Fix unit tests for volume connector and target * Add OSC commands for volume connector 1.14.0 ------ * Fix over-indent in \_validate\_obj() functions * Add volume target support to Python API * Add volume connector support to Python API * switch from oslosphinx to openstackdocstheme * reno: feature parity between ironic & OSC * Updated from global requirements * Log warning when API version is not specified for the OSC plugin * Improve help text for --local-link-connection * Update releasenote for osc-port-set * Add options for osc 'port set' command * Updated from global requirements * Add OSC 'baremetal driver raid property list' cmd * Add OSC 'baremetal driver property list' command * Create port with specific port group UUID in OSC * Adds --driver option to OSC "node list" command * Add --uuid option to OSC "port create" cmd * Remove support for py34 * Updated from global requirements * Add basic tests for OSC plugin baremetal chassis commands * Updated from global requirements * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements * Updated from global requirements 1.13.0 ------ * Updated from global requirements * Add usage documentation for Baremetal OSC Plugin * Updated from global requirements * Extends driver-list, driver-show supporting new hardware types * Updated from global requirements * Deduplicate method in OSC functional tests * Remove log translations 1.12.0 ------ * Update OSC baremetal node set/unset supporting dynamic drivers * Add missing 'autospec' statements to unit test mocks * Add space between items in exception message * OSC 'node list' recognizes all provision states * Add DRIVER\_RESOURCE to remove duplicated strings * Add tests for node list and show with specific fields * Add negative tests for baremetal node commands * Updated from global requirements * Updated from global requirements * Update test requirement * Updated from global requirements * Add testcases for OSC baremetal port group commands * Remove 'states' field from OSC CLI output * Updated from global requirements * Print pecan exceptions properly * Add basic tests for OSC plugin baremetal port commands * Add VIFs commands to help test * Handle log message interpolation by the logger * Support i18n for baremetal driver, portgroup cmds * Do not show chassis\_uuid field when it is not specified * Add test to create a port with specific port group UUID * Fix cleanup of resources in OSC plugin functional tests * Fix cleanup of resources in functional tests * Change tenant to project in docs and unit tests * Support i18n for baremetal node cmds * Use same variable for --[no-]maintenance * Add args to CLI 'node-create' for selecting hardware interfaces * Update reno for stable/ocata 1.11.0 ------ * Support --os-baremetal-api-version latest * Allow creating portgroups via create commands * Fix help message for the node-vif-attach command * Functional tests for port groups in ironicclient * Add --wait to OSC provisioning commands * Add --no-maintenance to OSC 'baremetal node list' * Follow up nits in the patch "ironic node-inject-nmi" * Updated from global requirements * Fix node-inject-nmi to pass an empty body * Add negative test-cases for openstack node create command * Typo fix: prefered => preferred * Fix ImportError when providing a meaningless API version * Add a new OSC command for Inject NMI 1.10.0 ------ * Support soft reboot and soft power off with timeout for OSC * Add soft reboot/poweroff power states * Change os\_tenant\_name to os\_project\_name in tests configuration script * Support i18n for baremetal chassis cmds * Support i18n for baremetal port cmds * Updated from global requirements * Replace yaml.load() with yaml.safe\_load() * Add a new command "ironic node-inject-nmi" * Extend VIF attach commands 1.9.0 ----- * Add interface attach/detach support * Update tox envs list * Fix multiple ports deletion * Raise on NodeManager get when invalid identifier provided * [trivial] Fix underline length under title in doc * [trivial] Fix of apostrophe in tox.ini * Simplify heading capitalization * Add mode and properties to portgroup OSC plugin * Add mode and properties to portgroup * Log warning if no property for (Un)Set commands * Pass argument as params in test\_port\_update * Add portgroup support to osc plugin * Simplify heading capitalization in shell's HelpFormatter * Describe possible exception in docstring * Fix API object representation in unittests * Verify JSON response of chassis commands * Add python API and CLI for port groups * Use oslo\_serialization.base64 to follow OpenStack Python3 * Updated from global requirements * Fix exception message creation in get\_client() * Describe possible exception in docstring * Add more tests to node\_shell * Updated from global requirements * Use identity api version 3 for OSC plugin tests * Strip endpoint version in OSC plugin * Update release note for fix to required args * Show team and repo badges on README * Add warning message for baremetal node set/unset commands * Allow import more than one func from i18n * Add sanity tests for baremetal power state commands * List required arguments in '--help' message in Ironic Client * Replace six.iteritems() with .items() * Add unit tests for OSC plugin * Use uuidutils instead of uuid.uuid4() * Add tests for maintenance mode commands * Add tests for provision state commands * Add test for ironic port-list command * OSC add capability to remove node/chassis\_uuid * Update to hacking 0.12.0 and use new checks * Updated from global requirements 1.8.0 ----- * Fix python3 compatibility when HTTP Error are returned * Add basic tests for OSC plugin baremetal node commands * Updated from global requirements * Avoid string interpolation in logging calls * Updated from global requirements * Use function import\_versioned\_module from oslo.utils * Updated from global requirements * Fixed json response func tests * Fix display of chassis UUID field if empty * Extend OSC "node list" cmd to fetch nodes without instance UUID * [trivial] Remove unused variables assignment * Updated from global requirements * Add plug-in summary for osc doc * Updated from global requirements * Set OSC default baremetal api version as in ironicclient * Fix import of ironicclient and reformat docstring * Update .gitignore to ignore .idea of PyCharm * Enable release notes translation * Add docs for create command * Refactor provision state so all actions can use inherited take\_action * If no resource, don't call Resource.to\_dict() * Updated from global requirements * Add prefix "$" for command examples * TrivialFix Remove white space between print and () * Use ConfigParser instead of SafeConfigParser in Python 3 * Hide 'nodes' field from chassis OSC subcommands output * Hide 'ports' field from node OSC subcommands output * 'ironic create' handles file args * Adds --chassis-uuid to osc 'baremetal node set' * standardize release note page names and ordering * Document updating nested node attributes with CLI * OSC plugin support microversions 1.21 & 1.22 * Update reno for stable/newton * Include jsonschema only once in requirements * use utils.key\_value\_pairs\_to\_dict() * Move duplicated info to new v1/utils.py 1.7.0 ----- * Correct a couple small grammar things in release notes * Clean up release notes for 1.7 * Change 'P' to 'Queens' and add deprecation date * Add openstack baremetal driver commands * Don't write python bytecode while testing * Add node validate OSC command * Add --chassis to 'openstack baremetal node list' * Sync tools/tox\_install.sh * Updated from global requirements * Using assertIsNone() is preferred over assertEqual() * Add --node to 'openstack baremetal port list' * Adds node boot device & passthru OSC commands * Set/unset node's target RAID config via OSC command * Add baremetal port list command to OSC plugin * Updated from global requirements * Add baremetal port delete command to OSC plugin * Add openstack baremetal chassis commands * Adds 'openstack baremetal node console' commands * Use osc-lib instead of openstackclient * Use osc\_lib instead of cliff * Update hacking test-requirement * Add docs target to tox.ini * Add --wait flag for provision actions and wait\_for\_provision\_state function * Use constraints for all the things * Trivial: Fix doc string for class DeleteBaremetalNode * Add key\_value\_pairs\_to\_dict() method * Updated from global requirements * Add 'openstack baremetal node adopt' command * Update help shown for node-delete * Add baremetal port set command to OSC plugin * Fail with more meaningful error while creating client * Deprecate -l option for port creation in OSC plugin * Trivial: remove redundant parentheses * Remove unused variables assignments in OSC plugin unit tests * Fix i18n problems in shell.py * Add create command to ironic client * Updated from global requirements * Fix uuid to UUID in expected error messages * Trivial: Remove useless spaces * Verify JSON response of driver commands * Make shell main() specify return value in exit code * Add baremetal port unset command to OSC plugin 1.6.0 ----- * Add support for node.resource\_class * Fix coverage target * Add CLI tests to check JSON response body * Add baremetal port show command to OSC plugin * Add internal\_info field to port * Remove unused LONG\_FIELDS * Remove discover from test-requirements * Trivial: Fix wrong comment in power state test 1.5.0 ----- * Updates supporting ironic-neutron integration * Add release note link for client release notes * Add Python 3.5 classifier and venv * Fix Quick-start example syntax * Negative tests for testing chassis-create command * Updated from global requirements 1.4.0 ----- * Fix py35 unit tests * Grammatical fixes for cache expiry feature * Change position in assert arguments * Add test for ironic node-list command * Updated from global requirements * Add test for ironic driver-list command * Add env var for version cache timeout * Add invalid attribute names to exception * Simplify use of config\_drive arg in osc * Add maintenance mode commands * Add provision state commands * Fix error returned by ironic --json node-validate * Negative tests for testing actions with Chassis * Updated from global requirements * Add test for 'node-show --field' command * Updated from global requirements * Tests for CLI help message * Move methods to utils.py * Updated from global requirements * Implementation of baremetal power state commands * Implementation of baremetal port create * Updated from global requirements * Add test for chassis-node-list * Fix quotation mark in docstring * Add test for 'port-show --field' command * Add sanity tests for testing actions with Port * Updated from global requirements * Catch RetriableConnectionFailures from KAuth and retry * Updated from global requirements * Bring OSC plugin inline with approved spec * Updated from global requirements * Use name randomizer from tempest * Updated from global requirements * Replace deprecated tempest-lib with tempest * Update the home-page with developer documentation * Trivial: ignore openstack/common in flake8 exclude list * Updated from global requirements * Fix typo in docstring of assertTableHeaders method * Fix for tox 'testenv:cover' command * Updated from global requirements * Updated from global requirements * Fix typos in docstrings and comments * Add test for 'chassis-show --field' command * Client addition for Active Node Creation verb adopt * Fix pep8 stderr warning regarding \_\_all\_\_ defined as list * Code style fix according to flake8 * Set endpoint\_override while doing session.request * Trivial: Fix incorrect comments in test\_baremetal.py * Remove httplib2 * Update reno for stable/mitaka * Updated from global requirements 1.2.0 ----- * Remove leftover use of args.os\_endpoint * Improve output of --json option * Updated from global requirements * Add CLI support for RAID configuration * Updated from global requirements * Use keystoneauth instead of keystoneclient * Don't ignore failures when delete nodes * Use requests lib in HTTPClient * Add a JSON option to the client * Do not pass endpoint to constructor in OSC * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * Log SHA1 hash of X-Auth-Token value * Stop ignoring \_ as builtin in pep8 * Remove unused \`anyjson\` * Updated from global requirements * Add sanity tests for testing actions with Chassis * Add 'node-set-provision-state clean' * continue to delete next node if failed with previous one * Updated from global requirements * Fix unit test 'Argument parse failed' error * Updated from global requirements * Replace HTTP 'magic numbers' with constants * Add CLI to list nodes using the same driver * Allow functional tests to work with Keystone v3 1.1.0 ----- * Allow to initialize keystone v3 client * Updated from global requirements * Support all API versions up to 1.latest * Put py34 first in the env order of tox * Updated from global requirements * Updated from global requirements * Fix params order in assertEqual * Updated from global requirements * Remove openstack-common.conf * Move ironicclient/common tests to their respective directory * Do not log secrets * Fix test cases of listing with provisioning state * Replace assertTrue with explicit assertIsInstance * Add first reno-based release note * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Add Sanity tests for testing actions with Driver * Replace assertEqual(None, \*) with assertIsNone in tests * Removes MANIFEST.in as it is not needed explicitely by PBR * Switch tox unit test command to use ostestr * Drop py33 support * Add --uuid to port-create * Add --uuid to chassis-create * Deprecated tox -downloadcache option removed * Scale back on how many warnings we issue * Refactoring and removing duplicate code of "base.Manager" heirs without changing the API * Correct node-port-list help info * Fix exceptions.from\_response() parameter * Updated from global requirements * Tests for testing node-set-power-state command * Add --wrap option for "ironic driver-properties" * Fix misprints in docstring * Updated from global requirements * Revert "Refactoring and removing duplicate code of "base.Manager" heirs" * Refactoring and removing duplicate code of "base.Manager" heirs * Updated from global requirements * Add sanity tests for testing actions with Node * Remove httpretty workaround * Use a dict to translate node power states to json data * Use requests-mock instead of httpretty * Updated from global requirements * Add missing translation markers * update node shell help info * replace LOG.warn with LOG.warning * Updated from global requirements 1.0.0 ----- * A minor change for driver\_shell test * Add documentation on how to run tests * Add driver-get-vendor-passthru-methods cmd * Add a new cmd method node-get-vendor-passthru-methods * Add more unit tests for clituils * Drop explicit Python 2.6 support * Make print\_list accept a list of dict * Sync with oslo-incubator * Last sync from oslo-incubator * Use keystoneclient.exception directly * Add simple table structure Ironic CLI tests * Introduce tempest-lib to functional tests 0.10.0 ------ * Updated from global requirements * Make sort keys the same for list commands * Updated from global requirements * Remove lxml requirement * Mock keystone call to avoid test failure * Add more filters for chassis node-list 0.9.0 ----- * Use doc8 style checker * Fix the bug of incorrect spelling * Replace six.iteritems() with .items() * Allow 'abort' verb for node-set-provision-state * Set a default endpoint value of None * Updated from global requirements * Added unit test cases for command-line shell * Introduce openstackclient plugin * Updated from global requirements * Remove unneeded param['detail'] setting 0.8.1 ----- * Fixes file cache TypeError * Fix functional tests job * Replace ConfigParser with six.moves.configparser 0.8.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added unit tests for command-line shell * Also retry on connection refused * Updated from global requirements * Updated from global requirements * Add provision\_updated\_at field to nodes * Filtering nodes by provision state * Replacing dict.iteritems() with dict.items() * Updated from global requirements * Fix version negotiation * Updated from global requirements * Allow specifying a set of fields of the Port and Chasis resources * Revert: requirement files in alphabetical order * Fix unittests due mock 1.1.0 release * Allow specifying a set of fields of the Node resource * Expose node's clean\_step and bump default version * Fix slow tests in RetriesTestCase * Updated from global requirements * Updated from global requirements * Also retry on HTTP 503 (service unavailable) * Cache negotiated api microversion for server * Updated from global requirements * Cleanup session creation * Updated from global requirements * Register global Keystone args first * Install the ironicclient into the 'venv' virtualenv * Updated from global requirements * Refactor resource\_fields.py 0.7.0 ----- * Disable meaningless sort keys in list command * httpretty can fail in Python 3.4 with wrong LC\_ALL * Add node-show-states command * Updated from global requirements * Revert fix that issues Unauthorized exception * Sync oslo.incubator * Consistent and more valid strings for Booleans * Drop use of 'oslo' namespace package * Disable invalid sort key in list command * Updated from global requirements * Add in support for a tox pypy target * Ensure \*-show input uuid is not empty * Remove unneeded 'utf-8' coding lines 0.6.0 ----- * Update README to work with release tools * Encode exception on cli for UnicodeDecodeError * Implement and enable retries on Conflict * Uncap library requirements for liberty 0.5.1 ----- * Client should fall back to the lower versions if necessary * Upgrade hacking to latest release * Fix node\_uuid option is required for port-create 0.5.0 ----- * Force LANGUAGE=en\_US in test runs * Add unittests for resource\_fields * Updated from global requirements * Add support for generating a config drive * Clean openstack-common module list * Send version header by default * Use oslo.i18n lib * Add support for logical names * Metavar name should be hyphenated * Avoid httpretty 0.8.8 as it can break unittests * Add support for inspection to node-set-provision-state * Fix the final PEP8 errors * Adds basic ironicclient functional testing * Fix two error strings in the CLI * Fix typo on patch 155624 * Enable ironicclient with --ironic-api-version 1.x * Fix PEP8 E121,E122,E123,E124,E125,E129 errors * Updated from global requirements * For flake8 check, make the 'E12' ignore be more granular * Updated from global requirements * Consistent names of args and metavars, and help strings * Remove unused OS\_TEST\_TIMEOUT variable * Capture stdout or stderr in tests * Updated from global requirements * Fix Python Ironic Client Documentation 0.4.1 ----- * Rename --configdrive to --config-drive * Fix argument for configdrive usage * Fix help string for port-list 0.4.0 ----- * port-list support get-port-from-mac * Update requirements.txt * cli support --os-endpoint * ironicclient node-set-maintenance to accept true/false * Updated from global requirements * Check if --config-drive is only used with provision state "active" * Add --config-drive to node-set-provision-state * Add driver\_internal\_info to node-show output 0.3.3 ----- * ironicclient handle faultstring when using SessionClient * Support setting non-string fields * Workflow documentation is now in infra-manual * Removed http proxy environment variable so that httpretty can work * Fix to properly issue an Unauthorized exception 0.3.2 ----- * Add option to specify node uuid in node-create subcommand * Add IRONIC\_URL to README * Fix log\_curl\_request API version duplication * Update README * Fix node-set-provision-state cmd's help strings * Updated from global requirements * Add maintenance\_reason to node-show output * Add 'API' to description of ironic command * Fix the usage comment of node-set-power-state cmd * Updated from global requirements * Fix sphinx warnings * VendorPassthru commands to support different HTTP methods * Sane parameters for node and driver vendor\_passthru() * Updated from global requirements * Sync apiclient from Oslo * Add node-set-maintenance command * Fix python-ironicclient crash * Adds tty password entry for ironicclient 0.3.1 ----- * Correct node CREATION\_ATTRIBUTE "uuid" * Updated from global requirements * Add keystone v3 CLI support * Stop using intersphinx * Bump hacking version * Add "ironic node-set-power-state" cmd unit test * Small fixes for utils/{common\_filters,common\_params\_for\_list} * Add unit tests for "ironic node-show" shell cmd * Remove unused command in tox.ini * Add unit tests for "ironic node-create" shell cmd * Add unit test for "ironic node-update" shell cmd * Updated from global requirements 0.3.0 ----- * Replace calls to Mock.assert\_not\_called * Add defaults to the CLI help strings * Updates to CLI doc * Add 'rebuild' option to node-set-provision-state * Update README with a bit more info * ironic client to use os\_region\_name if provided * Add unit test for "ironic port-update" shell cmd * Add unit tests for "ironic node-delete" shell cmd * handles keyboard interrupt * fixes help string for driver-list * Add 'bash-completion' to 'ironic help' response * List resources with detail * Add sort\_key and sort\_dir parameters to \*-list * Fix column headings regression due to switch to cliutils * Work toward Python 3.4 support and testing * Updated from global requirements * Fix misspelled class name AmbigiousAuthSystem * Optimize get\_by\_instance\_uuid * Show port by MAC address 0.2.1 ----- * Add /nodes/detail support * Trim trailing slash and version from endpoint * Updated from global requirements 0.2.0 ----- * Export 'client' and 'exc' modules * Add {set,get}\_boot\_device and get\_supported\_boot\_devices * Add driver-properties command * Add ironic cli support for vendor-passthru * Add pagination support to {node, port, chassis}-list * Expose auth\_ref in ironicclient client object * Add bash completion support for ironic cli * Remove aliases \`arg\` and \`env\` from utils * Use suitable assert * Add CONTRIBUTING.rst * Make a few minor updates to node shell help strings * Updated from global requirements * Add set\_provision\_state command * Add UTF-8 coding lines to all Python files * replace dict.iteritems() with six.iteritems(dict) * node-show to show the instance\_info field * removed py3kcompat module * Updated from global requirements * Reuse module \`cliutils\` from common code 0.1.4 ----- * Updated from global requirements * Sync latest code and reuse exceptions from oslo * node-get-console incorporate the changes in API * Adds documentation for ironicclient API * Remove py3kcompat module * Add documentation for ironic CLI * Updated from global requirements * Documentation for contributors * Fix docstring for client.get\_client() * Updated from global requirements * node-list to show the maintenance field * Add main developer page 0.1.3 ----- * Sync cliutils from oslo * Avoid traceback with insufficient auth credentials * Add support for 'driver-show' command * Updated from global requirements * Add set-console-mode, get-console commands * Sort requirement files in alphabetical order * Filtering nodes by maintenance mode * Remove shebang lines from code 0.1.2 ----- * Fix the test parameter order * Return 'maintenance' from node-show command * Fix params order in assertEqual * Remove vim header * Node power state is not printed after set * Return node\_uuid from a port-show cmd * Fix Iterface misspelling from node-validate cmd * Remove tox locale overrides * Add node.states() to the client library * Fix node-create help requiring chassis uuid * Updated from global requirements * Remove unused method 'string\_to\_bool' from utils 0.1.1 ----- * Reuse Resource from oslo * Sync apiclient and strutils from Oslo * Run unittest with python 3.3 * Drop python2.5 suport in ironicclient.common.http * Improve node-validate command output * Add set\_provision\_state to the client libs * ironic client should display chassis\_uuid value * Update openstack-common.conf list, sync with oslo (0d8f18b) 0.1.0 ----- * Rename nodes//state to nodes//states * Remove unused oslo-incubator modules * Updated from global requirements * driver-list command to show the list of conductors 0.0.1 ----- * Enable rebooting in the client lib and cli * Move from inheritance HTTPClient in Ironic client * Move six dependency to requirements.txt * Let CLI print exception traceback from 'debuginfo' * Add missing i18n support * Add node-validate to cli * Replace chassis\_id with chassis\_uuid on Nodes * Enable created\_at/updated\_at for port-show/chassis-show * Include 'chassis\_id' on node-show * Enable created\_at/updated\_at for port-show/chassis-show * Remove in-place try/except blocks from shell commands * Replace node\_id to node\_uuid on Ports * Support building wheels (PEP-427) * Shows 'last\_error' property for a node * Comply with new hacking release * Change id->uuid in ironic cli help messages * Updated from global requirements * Modifies CLI to show nodes by instance uuid * Remove Python 2.4 all() implementation * Deal with unicode strings * Custom output file on the print\_\*() functions * Add driver-list * Fix cmd usage msg for ironic port-create * Reorder imports * Rename variables called 'fixtures' * Comply with new hacking requirements * Transform print statement to print function * Add node-set-power-state to cli * Fixes Auth Token being sent as lambda function * Updated from global requirements * Sort the dict iterms * Replace basestring with six.string\_types * Use six.iteritems() for dict * Import urlutils module from oslo * Import httplib from six.moves * Transform print statement to print function * Import StringIO from six * Removes mox from ironicclient * Add chassis-node-list * Add node-port-list * Updated from global requirements * Allows multiple deletions * Add short command line option * Improve help strings * Remove duplicated do\_node\_show * Add missing chassis\_id arg to node-create * Improve error feedback * Updated from global requirements * Implement \*-update commands to resrouces * Change service\_type to baremetal * Split v1/shell.py into different files * Implement Node commands * Add port-create and port-delete * Stop HTTPClient from calling self.auth\_token() * Add chassis-create and chassis-delete * Add port-show and port-list * Implement chassis-list + more tests * Add tests for chassis * Add basic tests * Manager.\_list should always return a list * Implement chassis-show * Basic project structure * remove client suffix from command line client * Add initial files * Added .gitreview ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/LICENSE0000664000175000017500000002363700000000000016641 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.3122718 python-ironicclient-4.11.0/PKG-INFO0000664000175000017500000001035500000000000016722 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: python-ironicclient Version: 4.11.0 Summary: OpenStack Bare Metal Provisioning API Client Library Home-page: https://docs.openstack.org/python-ironicclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ================================== Python bindings for the Ironic API ================================== Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/python-ironicclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Overview -------- This is a client for the OpenStack `Bare Metal API `_. It provides: * a Python API: the ``ironicclient`` module, and * a command-line interfaces: ``openstack baremetal`` Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is on `opendev.org `_. ``python-ironicclient`` is licensed under the Apache License, Version 2.0, like the rest of OpenStack. .. contents:: Contents: :local: Project resources ----------------- * Documentation: https://docs.openstack.org/python-ironicclient/latest/ * Source: https://opendev.org/openstack/python-ironicclient * PyPi: https://pypi.org/project/python-ironicclient * Bugs: https://storyboard.openstack.org/#!/project/959 * Release notes: https://docs.openstack.org/releasenotes/python-ironicclient/ Python API ---------- Quick-start Example:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) ``openstack baremetal`` CLI --------------------------- The ``openstack baremetal`` command line interface is available when the bare metal plugin (included in this package) is used with the `OpenStackClient `_. There are two ways to install the OpenStackClient (python-openstackclient) package: * along with this python-ironicclient package:: # pip install python-ironicclient[cli] * directly:: # pip install python-openstackclient An example of creating a basic node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi An example of creating a port on a node:: $ openstack baremetal port create --node AA:BB:CC:DD:EE:FF An example of updating driver properties for a node:: $ openstack baremetal node set --driver-info ipmi_address= For more information about the ``openstack baremetal`` command and the subcommands available, run:: $ openstack help baremetal Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Requires-Python: >=3.6 Provides-Extra: cli Provides-Extra: test ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/README.rst0000664000175000017500000000504000000000000017307 0ustar00zuulzuul00000000000000================================== Python bindings for the Ironic API ================================== Team and repository tags ------------------------ .. image:: https://governance.openstack.org/tc/badges/python-ironicclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Overview -------- This is a client for the OpenStack `Bare Metal API `_. It provides: * a Python API: the ``ironicclient`` module, and * a command-line interfaces: ``openstack baremetal`` Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is on `opendev.org `_. ``python-ironicclient`` is licensed under the Apache License, Version 2.0, like the rest of OpenStack. .. contents:: Contents: :local: Project resources ----------------- * Documentation: https://docs.openstack.org/python-ironicclient/latest/ * Source: https://opendev.org/openstack/python-ironicclient * PyPi: https://pypi.org/project/python-ironicclient * Bugs: https://storyboard.openstack.org/#!/project/959 * Release notes: https://docs.openstack.org/releasenotes/python-ironicclient/ Python API ---------- Quick-start Example:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) ``openstack baremetal`` CLI --------------------------- The ``openstack baremetal`` command line interface is available when the bare metal plugin (included in this package) is used with the `OpenStackClient `_. There are two ways to install the OpenStackClient (python-openstackclient) package: * along with this python-ironicclient package:: # pip install python-ironicclient[cli] * directly:: # pip install python-openstackclient An example of creating a basic node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi An example of creating a port on a node:: $ openstack baremetal port create --node AA:BB:CC:DD:EE:FF An example of updating driver properties for a node:: $ openstack baremetal node set --driver-info ipmi_address= For more information about the ``openstack baremetal`` command and the subcommands available, run:: $ openstack help baremetal ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/bindep.txt0000664000175000017500000000017700000000000017630 0ustar00zuulzuul00000000000000# install fonts missing for PDF job # see https://bugs.launchpad.net/openstack-i18n/+bug/1935742 tex-gyre [platform:dpkg doc] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/0000775000175000017500000000000000000000000016366 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/requirements.txt0000664000175000017500000000052100000000000021650 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. openstackdocstheme>=2.2.0 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/0000775000175000017500000000000000000000000017666 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/api_v1.rst0000664000175000017500000000646400000000000021611 0ustar00zuulzuul00000000000000.. _api_v1: ======================= ironicclient Python API ======================= The ironicclient python API lets you access ironic, the OpenStack Bare Metal Provisioning Service. For example, to manipulate nodes, you interact with an :py:class:`ironicclient.v1.node.Node` object. You obtain access to nodes via attributes of the :py:class:`ironicclient.v1.client.Client` object. Usage ===== Get a Client object ------------------- First, create an :py:class:`ironicclient.v1.client.Client` instance by passing your credentials to :py:meth:`ironicclient.client.get_client()`. By default, the Bare Metal Provisioning system is configured so that only administrators (users with 'admin' role) have access. .. note:: Explicit instantiation of :py:class:`ironicclient.v1.client.Client` may cause errors since it doesn't verify provided arguments, using :py:meth:`ironicclient.client.get_client()` is preferred way to get client object. There are two different sets of credentials that can be used:: * ironic endpoint and auth token * Identity Service (keystone) credentials Using ironic endpoint and auth token .................................... An auth token and the ironic endpoint can be used to authenticate:: * os_auth_token: authentication token (from Identity Service) * ironic_url: ironic API endpoint, eg http://ironic.example.org:6385/v1 To create the client, you can use the API like so:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) Using Identity Service (keystone) credentials ............................................. These Identity Service credentials can be used to authenticate:: * os_username: name of user * os_password: user's password * os_auth_url: Identity Service endpoint for authorization * insecure: Boolean. If True, does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) * os_tenant_{name|id}: name or ID of tenant Also the following parameters are required when using the Identity API v3:: * os_user_domain_name: name of a domain the user belongs to, usually 'default' * os_project_domain_name: name of a domain the project belongs to, usually 'default' To create a client, you can use the API like so:: >>> from ironicclient import client >>> >>> kwargs = {'os_username': 'name', >>> 'os_password': 'password', >>> 'os_auth_url': 'http://keystone.example.org:5000/', >>> 'os_project_name': 'project'} >>> ironic = client.get_client(1, **kwargs) Perform ironic operations ------------------------- Once you have an :py:class:`ironicclient.v1.client.Client`, you can perform various tasks:: >>> ironic.driver.list() # list of drivers >>> ironic.node.list() # list of nodes >>> ironic.node.get(node_uuid) # information about a particular node When the Client needs to propagate an exception, it will usually raise an instance subclassed from :py:class:`ironicclient.common.apiclient.exceptions.ClientException`. Refer to the modules themselves, for more details. ironicclient Modules ==================== * :ref:`modindex` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/cli/0000775000175000017500000000000000000000000020435 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/cli/index.rst0000664000175000017500000000024400000000000022276 0ustar00zuulzuul00000000000000====================================== python-ironicclient User Documentation ====================================== .. toctree:: standalone osc_plugin_cli ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.236276 python-ironicclient-4.11.0/doc/source/cli/osc/0000775000175000017500000000000000000000000021221 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/cli/osc/v1/0000775000175000017500000000000000000000000021547 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/cli/osc/v1/index.rst0000664000175000017500000000211000000000000023402 0ustar00zuulzuul00000000000000Command Reference ================= List of released CLI commands available in openstack client. These commands can be referenced by doing ``openstack help baremetal``. ==================== baremetal allocation ==================== .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal allocation * ================= baremetal chassis ================= .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal chassis * ================ baremetal create ================ .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal create ================ baremetal driver ================ .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal driver * ============== baremetal node ============== .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal node * ============== baremetal port ============== .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal port * ================ baremetal volume ================ .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal volume * ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/cli/osc_plugin_cli.rst0000664000175000017500000000505200000000000024162 0ustar00zuulzuul00000000000000==================================================== ``openstack baremetal`` Command-Line Interface (CLI) ==================================================== .. program:: openstack baremetal .. highlight:: bash Synopsis ======== :program:`openstack [options] baremetal` [command-options] :program:`openstack help baremetal` Description =========== The OpenStack Client plugin interacts with the Bare Metal service through the ``openstack baremetal`` command line interface (CLI). To use the ``openstack`` CLI, the OpenStackClient (python-openstackclient) package must be installed. There are two ways to do this: * along with this python-ironicclient package:: $ pip install python-ironicclient[cli] * directly:: $ pip install python-openstackclient This CLI is provided by python-openstackclient and osc-lib projects: * https://opendev.org/openstack/python-openstackclient * https://opendev.org/openstack/osc-lib .. _osc-auth: Authentication -------------- To use the CLI, you must provide your OpenStack username, password, project, and auth endpoint. You can use configuration options ``--os-username``, ``--os-password``, ``--os-project-id`` (or ``--os-project-name``), and ``--os-auth-url``, or set the corresponding environment variables:: $ export OS_USERNAME=user $ export OS_PASSWORD=password $ export OS_PROJECT_NAME=project # or OS_PROJECT_ID $ export OS_PROJECT_DOMAIN_ID=default $ export OS_USER_DOMAIN_ID=default $ export OS_IDENTITY_API_VERSION=3 $ export OS_AUTH_URL=http://auth.example.com:5000/identity Getting help ============ To get a list of available (sub)commands and options, run:: $ openstack help baremetal To get usage and options of a command, run:: $ openstack help baremetal Examples ======== Get information about the openstack baremetal node create command:: $ openstack help baremetal node create Get a list of available drivers:: $ openstack baremetal driver list Enroll a node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi --driver-info ipmi_address=1.2.3.4 Get a list of nodes:: $ openstack baremetal node list The baremetal API version can be specified via: * environment variable OS_BAREMETAL_API_VERSION:: $ export OS_BAREMETAL_API_VERSION=1.25 * or optional command line argument --os-baremetal-api-version:: $ openstack baremetal port group list --os-baremetal-api-version 1.25 Command Reference ================= .. toctree:: :glob: :maxdepth: 3 osc/v1/* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/cli/standalone.rst0000664000175000017500000000555300000000000023327 0ustar00zuulzuul00000000000000===================================================== ``baremetal`` Standalone Command-Line Interface (CLI) ===================================================== .. program:: baremetal .. highlight:: bash Synopsis ======== :program:`baremetal [options]` [command-options] :program:`baremetal help` Description =========== The standalone ``baremetal`` tool allows interacting with the Bare Metal service without installing the OpenStack Client tool as in :doc:`osc_plugin_cli`. The standalone tool is mostly identical to its OSC counterpart, with two exceptions: #. No need to prefix commands with ``openstack``. #. No authentication is assumed by default. Check the :doc:`OSC CLI reference ` for a list of available commands. Inspector support ----------------- The standalone ``baremetal`` tool optionally supports the low-level bare metal introspection API provided by ironic-inspector_. If ironic-inspector-client_ is installed, its commands_ are automatically available (also without the ``openstack`` prefix). .. _ironic-inspector: https://docs.openstack.org/ironic-inspector/ .. _ironic-inspector-client: https://docs.openstack.org/python-ironic-inspector-client/ .. _commands: https://docs.openstack.org/python-ironic-inspector-client/latest/cli/index.html Standalone usage ---------------- To use the CLI with a standalone bare metal service, you need to provide an endpoint to connect to. It can be done in three ways: #. Provide an explicit ``--os-endpoint`` argument, e.g.: .. code-block:: bash $ baremetal --os-endpoint https://ironic.host:6385 node list #. Set the corresponding environment variable, e.g.: .. code-block:: bash $ export OS_ENDPOINT=https://ironic.host:6385 $ baremetal node list #. Populate a clouds.yaml_ file, setting ``baremetal_endpoint_override``, e.g.: .. code-block:: bash $ cat ~/.config/openstack/clouds.yaml clouds: ironic: auth_type: none baremetal_endpoint_override: http://127.0.0.1:6385 $ export OS_CLOUD=ironic $ baremetal node list `Inspector support`_ works similarly, but the ``clouds.yaml`` option is called ``baremetal_introspection_endpoint_override``. The two endpoints can be configured simultaneously, e.g.: .. code-block:: bash $ cat ~/.config/openstack/clouds.yaml clouds: ironic: auth_type: none baremetal_endpoint_override: http://127.0.0.1:6385 baremetal_introspection_endpoint_override: http://127.0.0.1:5050 $ export OS_CLOUD=ironic $ baremetal node list $ baremetal introspection list .. _clouds.yaml: https://docs.openstack.org/openstacksdk/latest/user/guides/connect_from_config.html Usage with OpenStack -------------------- The standalone CLI can also be used with the Bare Metal service installed as part of OpenStack. See :ref:`osc-auth` for information on the required input. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/conf.py0000664000175000017500000000524200000000000021170 0ustar00zuulzuul00000000000000# -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinxcontrib.apidoc', 'sphinx.ext.viewcode', 'openstackdocstheme', 'cliff.sphinxext', ] # sphinxcontrib.apidoc options apidoc_module_dir = '../../ironicclient' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'tests/functional/*', 'tests'] apidoc_separate_modules = True # openstackdocstheme options openstackdocs_repo_name = 'openstack/python-ironicclient' openstackdocs_pdf_link = True openstackdocs_use_storyboard = True # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = u'OpenStack Foundation' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['ironicclient.'] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of glob-style patterns that should be excluded when looking for # source files. They are matched against the source file names relative to the # source directory, using slashes as directory separators on all platforms. exclude_patterns = ['api/ironicclient.tests.functional.*'] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. #html_theme_path = ["."] #html_theme = '_theme' #html_static_path = ['_static'] html_theme = 'openstackdocs' # Output file base name for HTML help builder. htmlhelp_basename = 'python-ironicclientdoc' latex_use_xindy = False # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ( 'index', 'doc-python-ironicclient.tex', u'Python Ironic Client Documentation', u'OpenStack LLC', 'manual' ), ] autoprogram_cliff_application = 'openstack' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/contributor/0000775000175000017500000000000000000000000022240 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/contributor/contributing.rst0000664000175000017500000000275700000000000025514 0ustar00zuulzuul00000000000000.. _contributing: =================================== Contributing to python-ironicclient =================================== If you're interested in contributing to the python-ironicclient project, the following will help get you started. #openstack-ironic on OFTC IRC Network ------------------------------------- There is a very active chat channel at irc://irc.oftc.net/#openstack-ironic. This is usually the best place to ask questions and find your way around. IRC stands for Internet Relay Chat and it is a way to chat online in real time. You can ask a question and come back later to read the answer in the log files. Logs for the #openstack-ironic IRC channel are stored at http://eavesdrop.openstack.org/irclogs/%23openstack-ironic/. Contributor License Agreement ----------------------------- .. index:: single: license; agreement In order to contribute to the python-ironicclient project, you need to have signed OpenStack's contributor's agreement. .. seealso:: * https://docs.openstack.org/infra/manual/developers.html * https://wiki.openstack.org/wiki/CLA Project Hosting Details ----------------------- Bug tracker https://storyboard.openstack.org/#!/project/959 Mailing list (prefix subjects with ``[ironic]`` for faster responses) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss Code Hosting https://opendev.org/openstack/python-ironicclient Code Review https://review.opendev.org/#/q/status:open+project:openstack/python-ironicclient,n,z ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/contributor/index.rst0000664000175000017500000000026400000000000024103 0ustar00zuulzuul00000000000000============================================= python-ironicclient Contributor Documentation ============================================= .. toctree:: contributing testing ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/contributor/testing.rst0000664000175000017500000000364400000000000024456 0ustar00zuulzuul00000000000000.. _testing: ======= Testing ======= Python Guideline Enforcement ............................ All code has to pass the pep8 style guideline to merge into OpenStack, to validate the code against these guidelines you can run:: $ tox -e pep8 Unit Testing ............ It is strongly encouraged to run the unit tests locally under one or more test environments prior to submitting a patch. To run all the recommended environments sequentially and pep8 style guideline run:: $ tox You can also selectively pick specific test environments by listing your chosen environments after a -e flag:: $ tox -e py3,pep8 .. note:: Tox sets up virtual environment and installs all necessary dependencies. Sharing the environment with devstack testing is not recommended due to conflicting configuration with system dependencies. Functional Testing .................. Functional testing assumes the existence of the script run_functional.sh in the python-ironicclient/tools directory. The script run_functional.sh generates test.conf file. To run functional tests just run ./run_functional.sh. Also, the test.conf file could be created manually or generated from environment variables. It assumes the existence of an openstack cloud installation along with admin credentials. The test.conf file lives in ironicclient/tests/functional/ directory. To run functional tests in that way create test.conf manually and run:: $ tox -e functional An example test.conf file:: [functional] api_version = 1 os_auth_url=http://192.168.0.2:5000/v2.0/ os_username=admin os_password=admin os_project_name=admin If you are testing ironic in standalone mode, only the parameters 'auth_strategy', 'os_auth_token' and 'ironic_url' are required; all others will be ignored. An example test.conf file for standalone host:: [functional] auth_strategy = noauth os_auth_token = fake ironic_url = http://10.0.0.2:6385 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/index.rst0000664000175000017500000000122100000000000021523 0ustar00zuulzuul00000000000000=========================================== Python Bindings to the OpenStack Ironic API =========================================== This is a client for the OpenStack `Ironic`_ API. It provides: * a Python API: the ``ironicclient`` module, and * command-line interface: ``openstack baremetal`` Contents ======== .. toctree:: :maxdepth: 2 api_v1 cli/index user/create_command contributor/index reference/index Release Notes Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _Ironic: https://wiki.openstack.org/wiki/Ironic ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/reference/0000775000175000017500000000000000000000000021624 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/reference/index.rst0000664000175000017500000000024400000000000023465 0ustar00zuulzuul00000000000000======================================= Full Ironic Client Python API Reference ======================================= .. toctree:: :maxdepth: 1 api/modules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1645793348.2442753 python-ironicclient-4.11.0/doc/source/user/0000775000175000017500000000000000000000000020644 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/doc/source/user/create_command.rst0000664000175000017500000001327100000000000024343 0ustar00zuulzuul00000000000000=================================================== Creating the Bare Metal service resources from file =================================================== It is possible to create a set of resources using their descriptions in JSON or YAML format. It can be done in one of two ways: 1. Using OpenStackClient bare metal plugin CLI's command ``openstack baremetal create``:: $ openstack -h baremetal create usage: openstack baremetal create [-h] [ ...] Create resources from files positional arguments: File (.yaml or .json) containing descriptions of the resources to create. Can be specified multiple times. 2. Programmatically using the Python API: .. autofunction:: ironicclient.v1.create_resources.create_resources :noindex: File containing Resource Descriptions ===================================== The resources to be created can be described either in JSON or YAML. A file ending with ``.json`` is assumed to contain valid JSON, and a file ending with ``.yaml`` is assumed to contain valid YAML. Specifying a file with any other extension leads to an error. The resources that can be created are chassis, nodes, port groups and ports. A chassis can contain nodes (and resources of nodes) definitions nested under ``"nodes"`` key. A node can contain port groups definitions nested under ``"portgroups"``, and ports definitions under ``"ports"`` keys. Ports can be also nested under port groups in ``"ports"`` key. The schema used to validate the supplied data is the following:: { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for ironic resources file", "type": "object", "properties": { "chassis": { "type": "array", "items": { "type": "object" } }, "nodes": { "type": "array", "items": { "type": "object" } } }, "additionalProperties": False } More detailed description of the creation process can be seen in the following sections. Examples ======== Here is an example of the JSON file that can be passed to the ``create`` command:: { "chassis": [ { "description": "chassis 3 in row 23", "nodes": [ { "name": "node-3", "driver": "ipmi", "portgroups": [ { "name": "switch.cz7882.ports.1-2", "ports": [ { "address": "ff:00:00:00:00:00" }, { "address": "ff:00:00:00:00:01" } ] } ], "ports": [ { "address": "00:00:00:00:00:02" }, { "address": "00:00:00:00:00:03" } ], "driver_info": { "ipmi_address": "192.168.1.23", "ipmi_username": "BmcUsername", "ipmi_password": "BmcPassword", } }, { "name": "node-4", "driver": "ipmi", "ports": [ { "address": "00:00:00:00:00:04" }, { "address": "00:00:00:00:00:01" } ] } ] } ], "nodes": [ { "name": "node-5", "driver": "ipmi", "chassis_uuid": "74d93e6e-7384-4994-a614-fd7b399b0785", "ports": [ { "address": "00:00:00:00:00:00" } ] }, { "name": "node-6", "driver": "ipmi" } ] } Creation Process ================ #. The client deserializes the files' contents and validates that the top-level dictionary in each of them contains only "chassis" and/or "nodes" keys, and their values are lists. The creation process is aborted if any failure is encountered in this stage. The rest of the validation is done by the ironic-api service. #. Each resource is created via issuing a POST request (with the resource's dictionary representation in the body) to the ironic-api service. In the case of nested resources (``"nodes"`` key inside chassis, ``"portgroups"`` key inside nodes, ``"ports"`` key inside nodes or portgroups), the top-level resource is created first, followed by the sub-resources. For example, if a chassis contains a list of nodes, the chassis will be created first followed by the creation of each node. The same is true for ports and port groups described within nodes. #. If a resource could not be created, it does not stop the entire process. Any sub-resources of the failed resource will not be created, but otherwise, the rest of the resources will be created if possible. Any failed resources will be mentioned in the response. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.248275 python-ironicclient-4.11.0/ironicclient/0000775000175000017500000000000000000000000020303 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/__init__.py0000664000175000017500000000153200000000000022415 0ustar00zuulzuul00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version from ironicclient import client from ironicclient import exc as exceptions __version__ = pbr.version.VersionInfo('python-ironicclient').version_string() __all__ = ( 'client', 'exc', 'exceptions', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/client.py0000664000175000017500000001362500000000000022142 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from openstack import config from oslo_utils import importutils from ironicclient.common.i18n import _ from ironicclient import exc LOG = logging.getLogger(__name__) def get_client(api_version, auth_type=None, os_ironic_api_version=None, max_retries=None, retry_interval=None, session=None, valid_interfaces=None, interface=None, service_type=None, region_name=None, additional_headers=None, global_request_id=None, **kwargs): """Get an authenticated client, based on the credentials. :param api_version: the API version to use. Valid value: '1'. :param auth_type: type of keystoneauth auth plugin loader to use. :param os_ironic_api_version: ironic API version to use. :param max_retries: Maximum number of retries in case of conflict error :param retry_interval: Amount of time (in seconds) between retries in case of conflict error. :param session: An existing keystoneauth session. Will be created from kwargs if not provided. :param valid_interfaces: List of valid endpoint interfaces to use if the bare metal endpoint is not provided. :param interface: An alias for valid_interfaces. :param service_type: Bare metal endpoint service type. :param region_name: Name of the region to use when searching the bare metal endpoint. :param additional_headers: Additional headers that should be attached to every request passing through the client. Headers of the same name specified per request will take priority. :param global_request_id: A header (in the form of ``req-$uuid``) that will be passed on all requests. Enables cross project request id tracking. :param kwargs: all the other params that are passed to keystoneauth for session construction. """ # TODO(TheJulia): At some point, we should consider possibly noting # the "latest" flag for os_ironic_api_version to cause the client to # auto-negotiate to the greatest available version, however we do not # have the ability yet for a caller to cap the version, and will hold # off doing so until then. if auth_type is None: if 'endpoint' in kwargs: if 'token' in kwargs: auth_type = 'admin_token' else: auth_type = 'none' elif 'token' in kwargs and 'auth_url' in kwargs: auth_type = 'token' else: auth_type = 'password' if not session: # TODO(dtantsur): consider flipping load_yaml_config to True to support # the clouds.yaml format. region = config.get_cloud_region(load_yaml_config=False, load_envvars=False, auth_type=auth_type, **kwargs) session = region.get_session() # Make sure we also pass the endpoint interface to the HTTP client. # NOTE(gyee/efried): 'interface' in ksa config is deprecated in favor of # 'valid_interfaces'. So, since the caller may be deriving kwargs from # conf, accept 'valid_interfaces' first. But keep support for 'interface', # in case the caller is deriving kwargs from, say, an existing Adapter. interface = valid_interfaces or interface endpoint = kwargs.get('endpoint') if not endpoint: try: # endpoint will be used to get hostname # and port that will be used for API version caching. # NOTE(gyee): KSA defaults interface to 'public' if it is # empty or None so there's no need to set it to publicURL # explicitly. endpoint = session.get_endpoint( service_type=service_type or 'baremetal', interface=interface, region_name=region_name, ) except Exception as e: raise exc.AmbiguousAuthSystem( _('Must provide Keystone credentials or user-defined ' 'endpoint, error was: %s') % e) ironicclient_kwargs = { 'os_ironic_api_version': os_ironic_api_version, 'additional_headers': additional_headers, 'global_request_id': global_request_id, 'max_retries': max_retries, 'retry_interval': retry_interval, 'session': session, 'endpoint_override': endpoint, 'interface': interface } return Client(api_version, **ironicclient_kwargs) def Client(version, endpoint_override=None, session=None, *args, **kwargs): """Create a client of an appropriate version. This call requires a session. If you want it to be created, use ``get_client`` instead. :param endpoint_override: A bare metal endpoint to use. :param session: A keystoneauth session to use. This argument is actually required and is marked optional only for backward compatibility. :param args: Other arguments to pass to the HTTP client. Not recommended, use kwargs instead. :param kwargs: Other keyword arguments to pass to the HTTP client (e.g. ``insecure``). """ module = importutils.import_versioned_module('ironicclient', version, 'client') client_class = getattr(module, 'Client') return client_class(endpoint_override=endpoint_override, session=session, *args, **kwargs) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.248275 python-ironicclient-4.11.0/ironicclient/common/0000775000175000017500000000000000000000000021573 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/__init__.py0000664000175000017500000000000000000000000023672 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.248275 python-ironicclient-4.11.0/ironicclient/common/apiclient/0000775000175000017500000000000000000000000023543 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/apiclient/__init__.py0000664000175000017500000000000000000000000025642 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/apiclient/base.py0000664000175000017500000004104000000000000025026 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ # E1102: %s is not callable # pylint: disable=E1102 import abc import copy from http import client as http_client from urllib import parse as urlparse from oslo_utils import strutils from ironicclient.common.apiclient import exceptions from ironicclient.common.i18n import _ def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: if obj.uuid: return obj.uuid except AttributeError: pass try: return obj.id except AttributeError: return obj # TODO(aababilov): call run_hooks() in HookableMixin's child classes class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): """Add a new hook of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_func: hook function """ if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): """Run all hooks of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param args: args to be passed to every hook function :param kwargs: kwargs to be passed to every hook function """ hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) class BaseManager(HookableMixin): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None def __init__(self, client): """Initializes BaseManager with `client`. :param client: instance of BaseClient descendant for HTTP requests """ super(BaseManager, self).__init__() self.client = client def _list(self, url, response_key=None, obj_class=None, json=None): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) """ if json: body = self.client.post(url, json=json).json() else: body = self.client.get(url).json() if obj_class is None: obj_class = self.resource_class data = body[response_key] if response_key is not None else body # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): pass return [obj_class(self, res, loaded=True) for res in data if res] def _get(self, url, response_key=None): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. """ body = self.client.get(url).json() data = body[response_key] if response_key is not None else body return self.resource_class(self, data, loaded=True) def _head(self, url): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' """ resp = self.client.head(url) return resp.status_code == http_client.NO_CONTENT def _post(self, url, json, response_key=None, return_raw=False): """Create an object. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class """ body = self.client.post(url, json=json).json() data = body[response_key] if response_key is not None else body if return_raw: return data return self.resource_class(self, data) def _put(self, url, json=None, response_key=None): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ resp = self.client.put(url, json=json) # PUT requests may not return a body if resp.content: body = resp.json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _patch(self, url, json=None, response_key=None): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ body = self.client.patch(url, json=json).json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _delete(self, url): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' """ return self.client.delete(url) class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in kwargs.copy().items(): if ref is None: kwargs.pop(key) else: if isinstance(ref, Resource): kwargs.pop(key) kwargs['%s_id' % key] = getid(ref) return kwargs def create(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._post( self.build_url(**kwargs), {self.key: kwargs}, self.key) def get(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._get( self.build_url(**kwargs), self.key) def head(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._head(self.build_url(**kwargs)) def list(self, base_url=None, **kwargs): """List the collection. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % urlparse.urlencode(kwargs) if kwargs else '', }, self.collection_key) def put(self, base_url=None, **kwargs): """Update an element. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._put(self.build_url(base_url=base_url, **kwargs)) def update(self, **kwargs): kwargs = self._filter_kwargs(kwargs) params = kwargs.copy() params.pop('%s_id' % self.key) return self._patch( self.build_url(**kwargs), {self.key: params}, self.key) def delete(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._delete( self.build_url(**kwargs)) def find(self, base_url=None, **kwargs): """Find a single item with attributes matching ``**kwargs``. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % urlparse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(msg) elif num > 1: raise exceptions.NoUniqueMatch else: return rl[0] class Extension(HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') manager_class = None def __init__(self, name, module): super(Extension, self).__init__() self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) else: try: if issubclass(attr_value, BaseManager): self.manager_class = attr_value except TypeError: pass def __repr__(self): return "" % self.name class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def human_id(self): """Human-readable ID which can be used for bash completion.""" if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) self._add_details( {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False return self._info == other._info def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/apiclient/exceptions.py0000664000175000017500000003214600000000000026304 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Exception definitions. """ from http import client as http_client import inspect import sys from ironicclient.common.i18n import _ class ClientException(Exception): """The base exception class for all exceptions this library raises.""" pass class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class ConnectionError(ClientException): """Cannot connect to API service.""" pass class ConnectionRefused(ConnectionError): """Connection refused while trying to connect to API service.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions.""" http_status = 0 message = _("HTTP Error") def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = _("HTTP Client Error") class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = _("HTTP Server Error") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = http_client.MULTIPLE_CHOICES message = _("Multiple Choices") class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = http_client.BAD_REQUEST message = _("Bad Request") class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = http_client.UNAUTHORIZED message = _("Unauthorized") class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = http_client.PAYMENT_REQUIRED message = _("Payment Required") class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = http_client.FORBIDDEN message = _("Forbidden") class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = http_client.NOT_FOUND message = _("Not Found") class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = http_client.METHOD_NOT_ALLOWED message = _("Method Not Allowed") class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = http_client.NOT_ACCEPTABLE message = _("Not Acceptable") class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = http_client.PROXY_AUTHENTICATION_REQUIRED message = _("Proxy Authentication Required") class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = http_client.REQUEST_TIMEOUT message = _("Request Timeout") class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = http_client.CONFLICT message = _("Conflict") class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = http_client.GONE message = _("Gone") class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = http_client.LENGTH_REQUIRED message = _("Length Required") class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = http_client.PRECONDITION_FAILED message = _("Precondition Failed") class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = http_client.REQUEST_ENTITY_TOO_LARGE message = _("Request Entity Too Large") def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = http_client.REQUEST_URI_TOO_LONG message = _("Request-URI Too Long") class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = http_client.UNSUPPORTED_MEDIA_TYPE message = _("Unsupported Media Type") class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = http_client.REQUESTED_RANGE_NOT_SATISFIABLE message = _("Requested Range Not Satisfiable") class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = http_client.EXPECTATION_FAILED message = _("Expectation Failed") class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = http_client.UNPROCESSABLE_ENTITY message = _("Unprocessable Entity") class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = http_client.INTERNAL_SERVER_ERROR message = _("Internal Server Error") # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = http_client.NOT_IMPLEMENTED message = _("Not Implemented") class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = http_client.BAD_GATEWAY message = _("Bad Gateway") class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = http_client.SERVICE_UNAVAILABLE message = _("Service Unavailable") class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = http_client.GATEWAY_TIMEOUT message = _("Gateway Timeout") class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = http_client.HTTP_VERSION_NOT_SUPPORTED message = _("HTTP Version Not Supported") # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in vars(sys.modules[__name__]).items() if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ req_id = response.headers.get("x-openstack-request-id") # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": req_id, } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if isinstance(body, dict): error = body.get(list(body)[0]) if isinstance(error, dict): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or str(body)) elif content_type.startswith("text/"): kwargs["details"] = getattr(response, 'text', '') try: cls = _code_map[response.status_code] except KeyError: # 5XX status codes are server errors if response.status_code >= http_client.INTERNAL_SERVER_ERROR: cls = HttpServerError # 4XX status codes are client request errors elif (http_client.BAD_REQUEST <= response.status_code < http_client.INTERNAL_SERVER_ERROR): cls = HTTPClientError else: cls = HttpError return cls(**kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/base.py0000664000175000017500000003273200000000000023066 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ import abc import copy from urllib import parse as urlparse from ironicclient.common.apiclient import base from ironicclient import exc def getid(obj): """Wrapper to get object's ID. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: return obj.id except AttributeError: return obj class Manager(object, metaclass=abc.ABCMeta): """Provides CRUD operations with a particular API.""" def __init__(self, api): self.api = api def _path(self, resource_id=None): """Returns a request path for a given resource identifier. :param resource_id: Identifier of the resource to generate the request path. """ return ('/v1/%s/%s' % (self._resource_name, resource_id) if resource_id else '/v1/%s' % self._resource_name) @property @abc.abstractmethod def resource_class(self): """The resource class """ @property @abc.abstractmethod def _resource_name(self): """The resource name. """ def _get(self, resource_id, fields=None, os_ironic_api_version=None, global_request_id=None): """Retrieve a resource. :param resource_id: Identifier of the resource. :param fields: List of specific fields to be returned. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. :raises exc.ValidationError: For invalid resource_id arg value. """ if not resource_id: raise exc.ValidationError( "The identifier argument is invalid. " "Value provided: {!r}".format(resource_id)) if fields is not None: resource_id = '%s?fields=' % resource_id resource_id += ','.join(fields) try: return self._list( self._path(resource_id), os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id)[0] except IndexError: return None def _get_as_dict(self, resource_id, fields=None, os_ironic_api_version=None, global_request_id=None): """Retrieve a resource as a dictionary :param resource_id: Identifier of the resource. :param fields: List of specific fields to be returned. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. :returns: a dictionary representing the resource; may be empty """ resource = self._get(resource_id, fields=fields, os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) if resource: return resource.to_dict() else: return {} def _format_body_data(self, body, response_key): if response_key: try: data = body[response_key] except KeyError: return [] else: data = body if not isinstance(data, list): data = [data] return data def _list_pagination(self, url, response_key=None, obj_class=None, limit=None, os_ironic_api_version=None, global_request_id=None): """Retrieve a list of items. The Ironic API is configured to return a maximum number of items per request, (see Ironic's api.max_limit option). This iterates over the 'next' link (pagination) in the responses, to get the number of items specified by 'limit'. If 'limit' is None this function will continue pagination until there are no more values to be returned. :param url: a partial URL, e.g. '/nodes' :param response_key: the key to be looked up in response dictionary, e.g. 'nodes' :param obj_class: class for constructing the returned objects. :param limit: maximum number of items to return. If None returns everything. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. """ if obj_class is None: obj_class = self.resource_class if limit is not None: limit = int(limit) kwargs = {"headers": {}} if os_ironic_api_version is not None: kwargs['headers'][ 'X-OpenStack-Ironic-API-Version'] = os_ironic_api_version if global_request_id is not None: kwargs["headers"]["X-Openstack-Request-Id"] = global_request_id # NOTE(jroll) # endpoint_trimmed is what is prepended if we only pass a path # to json_request. This might be something like # https://example.com:4443/baremetal # The code below which parses the 'next' URL keeps any path prefix # we're using, so it ends up calling json_request() with e.g. # /baremetal/v1/nodes. When this is appended to endpoint_trimmed, # we end up with a full URL of e.g. # https://example.com:4443/baremetal/baremetal/v1/nodes. # Parse out the prefix from the base endpoint here, so we can strip # it below and make sure we're passing a correct path. endpoint_parts = urlparse.urlparse(self.api.endpoint_trimmed) url_path_prefix = endpoint_parts[2] object_list = [] object_count = 0 limit_reached = False while url: resp, body = self.api.json_request('GET', url, **kwargs) data = self._format_body_data(body, response_key) for obj in data: object_list.append(obj_class(self, obj, loaded=True)) object_count += 1 if limit and object_count >= limit: # break the for loop limit_reached = True break # break the while loop and return if limit_reached: break url = body.get('next') if url: # NOTE(lucasagomes): We need to edit the URL to remove # the scheme and netloc url_parts = list(urlparse.urlparse(url)) url_parts[0] = url_parts[1] = '' if url_path_prefix: url_parts[2] = url_parts[2].replace( url_path_prefix, '', 1) url = urlparse.urlunparse(url_parts) return object_list def __list(self, url, response_key=None, body=None, os_ironic_api_version=None, global_request_id=None): kwargs = {"headers": {}} if os_ironic_api_version is not None: kwargs['headers'][ 'X-OpenStack-Ironic-API-Version'] = os_ironic_api_version if global_request_id is not None: kwargs["headers"]["X-Openstack-Request-Id"] = global_request_id resp, body = self.api.json_request('GET', url, **kwargs) data = self._format_body_data(body, response_key) return data def _list(self, url, response_key=None, obj_class=None, body=None, os_ironic_api_version=None, global_request_id=None): if obj_class is None: obj_class = self.resource_class data = self.__list(url, response_key=response_key, body=body, os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) return [obj_class(self, res, loaded=True) for res in data if res] def _list_primitives(self, url, response_key=None, os_ironic_api_version=None, global_request_id=None): return self.__list(url, response_key=response_key, os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) def _update(self, resource_id, patch, method='PATCH', os_ironic_api_version=None, global_request_id=None, params=None): """Update a resource. :param resource_id: Resource identifier. :param patch: New version of a given resource, a dictionary or None. :param method: Name of the method for the request. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. :param params: query parameters to pass. """ url = self._path(resource_id) kwargs = {"headers": {}} if patch is not None: kwargs['body'] = patch if os_ironic_api_version is not None: kwargs['headers'][ 'X-OpenStack-Ironic-API-Version'] = os_ironic_api_version if global_request_id is not None: kwargs["headers"]["X-Openstack-Request-Id"] = global_request_id if params: kwargs['params'] = params resp, body = self.api.json_request(method, url, **kwargs) # PATCH/PUT requests may not return a body if body: return self.resource_class(self, body) def _delete(self, resource_id, os_ironic_api_version=None, global_request_id=None): """Delete a resource. :param resource_id: Resource identifier. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. """ headers = {} if os_ironic_api_version is not None: headers["X-OpenStack-Ironic-API-Version"] = os_ironic_api_version if global_request_id is not None: headers["X-Openstack-Request-Id"] = global_request_id self.api.raw_request('DELETE', self._path(resource_id), headers=headers) class CreateManager(Manager, metaclass=abc.ABCMeta): """Provides creation operations with a particular API.""" @property @abc.abstractmethod def _creation_attributes(self): """A list of required creation attributes for a resource type. """ def create(self, os_ironic_api_version=None, global_request_id=None, **kwargs): """Create a resource based on a kwargs dictionary of attributes. :param kwargs: A dictionary containing the attributes of the resource that will be created. :param os_ironic_api_version: String version (e.g. "1.35") to use for the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. :raises exc.InvalidAttribute: For invalid attributes that are not needed to create the resource. """ new = {} invalid = [] for (key, value) in kwargs.items(): if key in self._creation_attributes: new[key] = value else: invalid.append(key) if invalid: raise exc.InvalidAttribute( 'The attribute(s) "%(attrs)s" are invalid; they are not ' 'needed to create %(resource)s.' % {'resource': self._resource_name, 'attrs': '","'.join(invalid)}) headers = {} if os_ironic_api_version is not None: headers['X-OpenStack-Ironic-API-Version'] = os_ironic_api_version if global_request_id is not None: headers["X-Openstack-Request-Id"] = global_request_id url = self._path() resp, body = self.api.json_request('POST', url, body=new, headers=headers) if body: return self.resource_class(self, body) class Resource(base.Resource): """Represents a particular instance of an object (tenant, user, etc). This is pretty much just a bag for attributes. """ def to_dict(self): return copy.deepcopy(self._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/filecache.py0000664000175000017500000000637400000000000024062 0ustar00zuulzuul00000000000000# # Copyright 2015 Rackspace, Inc # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import os import appdirs import dogpile.cache LOG = logging.getLogger(__name__) AUTHOR = 'openstack' PROGNAME = 'python-ironicclient' CACHE = None CACHE_DIR = appdirs.user_cache_dir(PROGNAME, AUTHOR) CACHE_EXPIRY_ENV_VAR = 'IRONICCLIENT_CACHE_EXPIRY' # environment variable CACHE_FILENAME = os.path.join(CACHE_DIR, 'ironic-api-version.dbm') DEFAULT_EXPIRY = 300 # seconds def _get_cache(): """Configure file caching.""" global CACHE if CACHE is None: # Ensure cache directory present if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) # Use the cache expiry if specified in an env var expiry_time = os.environ.get(CACHE_EXPIRY_ENV_VAR, DEFAULT_EXPIRY) try: expiry_time = int(expiry_time) except ValueError: LOG.warning("Environment variable %(env_var)s should be an " "integer (not '%(curr_val)s'). Using default " "expiry of %(default)s seconds instead.", {'env_var': CACHE_EXPIRY_ENV_VAR, 'curr_val': expiry_time, 'default': DEFAULT_EXPIRY}) expiry_time = DEFAULT_EXPIRY CACHE = dogpile.cache.make_region(key_mangler=str).configure( 'dogpile.cache.dbm', expiration_time=expiry_time, arguments={ "filename": CACHE_FILENAME, } ) return CACHE def _build_key(host, port): """Build a key based upon the hostname or address supplied.""" return "%s:%s" % (host, port) def save_data(host, port, data): """Save 'data' for a particular 'host' in the appropriate cache dir. param host: The host that we need to save data for param port: The port on the host that we need to save data for param data: The data we want saved """ key = _build_key(host, port) _get_cache().set(key, data) def retrieve_data(host, port, expiry=None): """Retrieve the version stored for an ironic 'host', if it's not stale. Check to see if there is valid cached data for the host/port combination and return that if it isn't stale. param host: The host that we need to retrieve data for param port: The port on the host that we need to retrieve data for param expiry: The age in seconds before cached data is deemed invalid """ # Ensure that a cache file exists first if not os.path.isfile(CACHE_FILENAME): return None key = _build_key(host, port) data = _get_cache().get(key, expiration_time=expiry) if data == dogpile.cache.api.NO_VALUE: return None return data ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/http.py0000664000175000017500000004605400000000000023135 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from distutils.version import StrictVersion import functools from http import client as http_client import json import logging import re import textwrap import time from urllib import parse as urlparse from keystoneauth1 import adapter from keystoneauth1 import exceptions as kexc from ironicclient.common import filecache from ironicclient.common.i18n import _ from ironicclient import exc # NOTE(deva): Record the latest version that this client was tested with. # We still have a lot of work to do in the client to implement # microversion support in the client properly! See # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' LAST_KNOWN_API_VERSION = 78 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) USER_AGENT = 'python-ironicclient' CHUNKSIZE = 1024 * 64 # 64kB _MAJOR_VERSION = 1 API_VERSION = '/v%d' % _MAJOR_VERSION API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default') DEFAULT_MAX_RETRIES = 5 DEFAULT_RETRY_INTERVAL = 2 SENSITIVE_HEADERS = ('X-Auth-Token',) SUPPORTED_ENDPOINT_SCHEME = ('http', 'https') _API_VERSION_RE = re.compile(r'/+(v%d)?/*$' % _MAJOR_VERSION) def _trim_endpoint_api_version(url): """Trim API version and trailing slash from endpoint.""" return re.sub(_API_VERSION_RE, '', url) def _extract_error_json(body): """Return error_message from the HTTP response body.""" try: body_json = json.loads(body) except ValueError: return {} if 'error_message' not in body_json: return {} try: error_json = json.loads(body_json['error_message']) except ValueError: return body_json err_msg = (error_json.get('faultstring') or error_json.get('description')) if err_msg: body_json['error_message'] = err_msg return body_json def get_server(url): """Extract and return the server & port.""" if url is None: return None, None parts = urlparse.urlparse(url) return parts.hostname, str(parts.port) class VersionNegotiationMixin(object): def negotiate_version(self, conn, resp): """Negotiate the server version Assumption: Called after receiving a 406 error when doing a request. :param conn: A connection object :param resp: The response object from http request """ def _query_server(conn): if (self.os_ironic_api_version and not isinstance(self.os_ironic_api_version, list) and self.os_ironic_api_version != 'latest'): base_version = ("/v%s" % str(self.os_ironic_api_version).split('.')[0]) else: base_version = API_VERSION # Raise exception on client or server error. resp = self._make_simple_request(conn, 'GET', base_version) if not resp.ok: raise exc.from_response(resp, method='GET', url=base_version) return resp version_overridden = False if (resp and hasattr(resp, 'request') and hasattr(resp.request, 'headers')): orig_hdr = resp.request.headers # Get the version of the client's last request and fallback # to the default for things like unit tests to not cause # migraines. req_api_ver = orig_hdr.get('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) else: req_api_ver = self.os_ironic_api_version if (resp and req_api_ver != self.os_ironic_api_version and self.api_version_select_state == 'negotiated'): # If we have a non-standard api version on the request, # but we think we've negotiated, then the call was overridden. # We should report the error with the called version requested_version = req_api_ver # And then we shouldn't save the newly negotiated # version of this negotiation because we have been # overridden a request. version_overridden = True else: requested_version = self.os_ironic_api_version if not resp: resp = _query_server(conn) if self.api_version_select_state not in API_VERSION_SELECTED_STATES: raise RuntimeError( _('Error: self.api_version_select_state should be one of the ' 'values in: "%(valid)s" but had the value: "%(value)s"') % {'valid': ', '.join(API_VERSION_SELECTED_STATES), 'value': self.api_version_select_state}) min_ver, max_ver = self._parse_version_headers(resp) # NOTE: servers before commit 32fb6e99 did not return version headers # on error, so we need to perform a GET to determine # the supported version range if not max_ver: LOG.debug('No version header in response, requesting from server') resp = _query_server(conn) min_ver, max_ver = self._parse_version_headers(resp) # Reset the maximum version that we permit if StrictVersion(max_ver) > StrictVersion(LATEST_VERSION): LOG.debug("Remote API version %(max_ver)s is greater than the " "version supported by ironicclient. Maximum available " "version is %(client_ver)s", {'max_ver': max_ver, 'client_ver': LATEST_VERSION}) max_ver = LATEST_VERSION # If the user requested an explicit version or we have negotiated a # version and still failing then error now. The server could # support the version requested but the requested operation may not # be supported by the requested version. # TODO(TheJulia): We should break this method into several parts, # such as a sanity check/error method. if ((self.api_version_select_state == 'user' and not self._must_negotiate_version()) or (self.api_version_select_state == 'negotiated' and version_overridden)): raise exc.UnsupportedVersion(textwrap.fill( _("Requested API version %(req)s is not supported by the " "server, client, or the requested operation is not " "supported by the requested version. " "Supported version range is %(min)s to " "%(max)s") % {'req': requested_version, 'min': min_ver, 'max': max_ver})) if (self.api_version_select_state == 'negotiated'): raise exc.UnsupportedVersion(textwrap.fill( _("No API version was specified or the requested operation " "was not supported by the client's negotiated API version " "%(req)s. Supported version range is: %(min)s to %(max)s") % {'req': requested_version, 'min': min_ver, 'max': max_ver})) if isinstance(requested_version, str): if requested_version == 'latest': negotiated_ver = max_ver else: negotiated_ver = str( min(StrictVersion(requested_version), StrictVersion(max_ver))) elif isinstance(requested_version, list): if 'latest' in requested_version: raise ValueError(textwrap.fill( _("The 'latest' API version can not be requested " "in a list of versions. Please explicitly request " "'latest' or request only versios between " "%(min)s to %(max)s") % {'min': min_ver, 'max': max_ver})) versions = [] for version in requested_version: if min_ver <= StrictVersion(version) <= max_ver: versions.append(StrictVersion(version)) if versions: negotiated_ver = str(max(versions)) else: raise exc.UnsupportedVersion(textwrap.fill( _("Requested API version specified and the requested " "operation was not supported by the client's " "requested API version %(req)s. Supported " "version range is: %(min)s to %(max)s") % {'req': requested_version, 'min': min_ver, 'max': max_ver})) else: raise ValueError(textwrap.fill( _("Requested API version %(req)s type is unsupported. " "Valid types are Strings such as '1.1', 'latest' " "or a list of string values representing API versions.") % {'req': requested_version})) if StrictVersion(negotiated_ver) < StrictVersion(min_ver): negotiated_ver = min_ver # server handles microversions, but doesn't support # the requested version, so try a negotiated version self.api_version_select_state = 'negotiated' self.os_ironic_api_version = negotiated_ver LOG.debug('Negotiated API version is %s', negotiated_ver) # Cache the negotiated version for this server endpoint_override = getattr(self, 'endpoint_override', None) host, port = get_server(endpoint_override) filecache.save_data(host=host, port=port, data=negotiated_ver) return negotiated_ver def _generic_parse_version_headers(self, accessor_func): min_ver = accessor_func('X-OpenStack-Ironic-API-Minimum-Version', None) max_ver = accessor_func('X-OpenStack-Ironic-API-Maximum-Version', None) return min_ver, max_ver def _parse_version_headers(self, accessor_func): # NOTE(jlvillal): Declared for unit testing purposes raise NotImplementedError() def _make_simple_request(self, conn, method, url): # NOTE(jlvillal): Declared for unit testing purposes raise NotImplementedError() def _must_negotiate_version(self): return (self.api_version_select_state == 'user' and (self.os_ironic_api_version == 'latest' or isinstance(self.os_ironic_api_version, list))) _RETRY_EXCEPTIONS = (exc.Conflict, exc.ServiceUnavailable, exc.ConnectionRefused, kexc.RetriableConnectionFailure) def with_retries(func): """Wrapper for _http_request adding support for retries.""" @functools.wraps(func) def wrapper(self, url, method, **kwargs): if self.conflict_max_retries is None: self.conflict_max_retries = DEFAULT_MAX_RETRIES if self.conflict_retry_interval is None: self.conflict_retry_interval = DEFAULT_RETRY_INTERVAL num_attempts = self.conflict_max_retries + 1 for attempt in range(1, num_attempts + 1): try: return func(self, url, method, **kwargs) except _RETRY_EXCEPTIONS as error: msg = ("Error contacting Ironic server: %(error)s. " "Attempt %(attempt)d of %(total)d" % {'attempt': attempt, 'total': num_attempts, 'error': error}) if attempt == num_attempts: LOG.error(msg) raise else: LOG.debug(msg) time.sleep(self.conflict_retry_interval) return wrapper class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def __init__(self, os_ironic_api_version, api_version_select_state, max_retries, retry_interval, **kwargs): self.os_ironic_api_version = os_ironic_api_version self.api_version_select_state = api_version_select_state self.conflict_max_retries = max_retries self.conflict_retry_interval = retry_interval if isinstance(kwargs.get('endpoint_override'), str): kwargs['endpoint_override'] = _trim_endpoint_api_version( kwargs['endpoint_override']) super(SessionClient, self).__init__(**kwargs) endpoint_filter = self._get_endpoint_filter() endpoint = self.get_endpoint(**endpoint_filter) if endpoint is None: raise exc.EndpointNotFound( _('The Bare Metal API endpoint cannot be detected and was ' 'not provided explicitly')) self.endpoint_trimmed = _trim_endpoint_api_version(endpoint) def _parse_version_headers(self, resp): return self._generic_parse_version_headers(resp.headers.get) def _get_endpoint_filter(self): return { 'interface': self.interface, 'service_type': self.service_type, 'region_name': self.region_name } def _make_simple_request(self, conn, method, url): # NOTE: conn is self.session for this class return conn.request(url, method, raise_exc=False, user_agent=USER_AGENT, endpoint_filter=self._get_endpoint_filter(), endpoint_override=self.endpoint_override) @with_retries def _http_request(self, url, method, **kwargs): # NOTE(TheJulia): self.os_ironic_api_version is reset in # the self.negotiate_version() call if negotiation occurs. if self.os_ironic_api_version and self._must_negotiate_version(): self.negotiate_version(self.session, None) kwargs.setdefault('user_agent', USER_AGENT) kwargs.setdefault('auth', self.auth) if isinstance(self.endpoint_override, str): kwargs.setdefault('endpoint_override', self.endpoint_override) if getattr(self, 'os_ironic_api_version', None): kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) for k, v in self.additional_headers.items(): kwargs['headers'].setdefault(k, v) if self.global_request_id is not None: kwargs['headers'].setdefault( "X-OpenStack-Request-ID", self.global_request_id) endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', self.interface) endpoint_filter.setdefault('service_type', self.service_type) endpoint_filter.setdefault('region_name', self.region_name) resp = self.session.request(url, method, raise_exc=False, **kwargs) if resp.status_code == http_client.NOT_ACCEPTABLE: negotiated_ver = self.negotiate_version(self.session, resp) kwargs['headers']['X-OpenStack-Ironic-API-Version'] = ( negotiated_ver) return self._http_request(url, method, **kwargs) if resp.status_code >= http_client.BAD_REQUEST: error_json = _extract_error_json(resp.content) raise exc.from_response(resp, error_json.get('error_message'), error_json.get('debuginfo'), method, url) elif resp.status_code in (http_client.MOVED_PERMANENTLY, http_client.FOUND, http_client.USE_PROXY): # Redirected. Reissue the request to the new location. location = resp.headers.get('location') resp = self._http_request(location, method, **kwargs) elif resp.status_code == http_client.MULTIPLE_CHOICES: raise exc.from_response(resp, method=method, url=url) return resp def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['json'] = kwargs.pop('body') resp = self._http_request(url, method, **kwargs) body = resp.content content_type = resp.headers.get('content-type', None) status = resp.status_code if (status in (http_client.NO_CONTENT, http_client.RESET_CONTENT) or content_type is None): return resp, list() if 'application/json' in content_type: try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) def _construct_http_client(session, token=None, auth_ref=None, os_ironic_api_version=DEFAULT_VER, api_version_select_state='default', max_retries=DEFAULT_MAX_RETRIES, retry_interval=DEFAULT_RETRY_INTERVAL, timeout=600, ca_file=None, cert_file=None, key_file=None, insecure=None, **kwargs): kwargs.setdefault('service_type', 'baremetal') kwargs.setdefault('user_agent', 'python-ironicclient') kwargs.setdefault('interface', kwargs.pop('endpoint_type', 'publicURL')) ignored = {'token': token, 'auth_ref': auth_ref, 'timeout': timeout != 600, 'ca_file': ca_file, 'cert_file': cert_file, 'key_file': key_file, 'insecure': insecure} dvars = [k for k, v in ignored.items() if v] if dvars: LOG.warning('The following arguments are ignored when using ' 'the session to construct a client: %s', ', '.join(dvars)) return SessionClient(session=session, os_ironic_api_version=os_ironic_api_version, api_version_select_state=api_version_select_state, max_retries=max_retries, retry_interval=retry_interval, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/i18n.py0000664000175000017500000000157200000000000022731 0ustar00zuulzuul00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. try: import oslo_i18n except ImportError: def _(msg): return msg else: _translators = oslo_i18n.TranslatorFactory(domain='ironicclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/common/utils.py0000664000175000017500000003654300000000000023320 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import base64 import contextlib import gzip import json import os import shutil import subprocess import sys import tempfile import time from oslo_utils import strutils import yaml from ironicclient.common.i18n import _ from ironicclient import exc class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): super(HelpFormatter, self).start_section(heading.capitalize()) def define_command(subparsers, command, callback, cmd_mapper): """Define a command in the subparsers collection. :param subparsers: subparsers collection where the command will go :param command: command name :param callback: function that will be used to process the command """ desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS) cmd_mapper[command] = subparser required_args = subparser.add_argument_group(_("Required arguments")) for (args, kwargs) in arguments: if kwargs.get('required'): required_args.add_argument(*args, **kwargs) else: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def define_commands_from_module(subparsers, command_module, cmd_mapper): """Add *do_* methods in a module and add as commands into a subparsers.""" for method_name in (a for a in dir(command_module) if a.startswith('do_')): # Commands should be hypen-separated instead of underscores. command = method_name[3:].replace('_', '-') callback = getattr(command_module, method_name) define_command(subparsers, command, callback, cmd_mapper) def split_and_deserialize(string): """Split and try to JSON deserialize a string. Gets a string with the KEY=VALUE format, split it (using '=' as the separator) and try to JSON deserialize the VALUE. :returns: A tuple of (key, value). """ try: key, value = string.split("=", 1) except ValueError: raise exc.CommandError(_('Attributes must be a list of ' 'PATH=VALUE not "%s"') % string) try: value = json.loads(value) except ValueError: pass return (key, value) def key_value_pairs_to_dict(key_value_pairs): """Convert a list of key-value pairs to a dictionary. :param key_value_pairs: a list of strings, each string is in the form = :returns: a dictionary, possibly empty """ if key_value_pairs: return dict(split_and_deserialize(v) for v in key_value_pairs) return {} def args_array_to_dict(kwargs, key_to_convert): """Convert the value in a dictionary entry to a dictionary. From the kwargs dictionary, converts the value of the key_to_convert entry from a list of key-value pairs to a dictionary. :param kwargs: a dictionary :param key_to_convert: the key (in kwargs), whose value is expected to be a list of key=value strings. This value will be converted to a dictionary. :returns: kwargs, the (modified) dictionary """ values_to_convert = kwargs.get(key_to_convert) if values_to_convert: kwargs[key_to_convert] = key_value_pairs_to_dict(values_to_convert) return kwargs def args_array_to_patch(op, attributes): patch = [] for attr in attributes: # Sanitize if not attr.startswith('/'): attr = '/' + attr if op in ['add', 'replace']: path, value = split_and_deserialize(attr) patch.append({'op': op, 'path': path, 'value': value}) elif op == "remove": # For remove only the key is needed patch.append({'op': op, 'path': attr}) else: raise exc.CommandError(_('Unknown PATCH operation: %s') % op) return patch def convert_list_props_to_comma_separated(data, props=None): """Convert the list-type properties to comma-separated strings :param data: the input dict object. :param props: the properties whose values will be converted. Default to None to convert all list-type properties of the input. :returns: the result dict instance. """ result = dict(data) if props is None: props = data.keys() for prop in props: val = data.get(prop, None) if isinstance(val, list): result[prop] = ', '.join(map(str, val)) return result def common_params_for_list(args, fields, field_labels): """Generate 'params' dict that is common for every 'list' command. :param args: arguments from command line. :param fields: possible fields for sorting. :param field_labels: possible field labels for sorting. :returns: a dict with params to pass to the client method. """ params = {} if args.marker is not None: params['marker'] = args.marker if args.limit is not None: if args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % args.limit) params['limit'] = args.limit if args.sort_key is not None: # Support using both heading and field name for sort_key fields_map = dict(zip(field_labels, fields)) fields_map.update(zip(fields, fields)) try: sort_key = fields_map[args.sort_key] except KeyError: raise exc.CommandError( _("%(sort_key)s is an invalid field for sorting, " "valid values for --sort-key are: %(valid)s") % {'sort_key': args.sort_key, 'valid': list(fields_map)}) params['sort_key'] = sort_key if args.sort_dir is not None: if args.sort_dir not in ('asc', 'desc'): raise exc.CommandError( _("%s is an invalid value for sort direction, " "valid values for --sort-dir are: 'asc', 'desc'") % args.sort_dir) params['sort_dir'] = args.sort_dir params['detail'] = args.detail requested_fields = args.fields[0] if args.fields else None if requested_fields is not None: params['fields'] = requested_fields return params def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None, fields=None, detail=False): """Generate common filters for any list request. :param marker: entity ID from which to start returning entities. :param limit: maximum number of entities to return. :param sort_key: field to use for sorting. :param sort_dir: direction of sorting: 'asc' or 'desc'. :param fields: a list with a specified set of fields of the resource to be returned. :param detail: Boolean, True to return detailed information. This parameter can be used for resources which accept 'detail' as a URL parameter. :returns: list of string filters. """ filters = [] if isinstance(limit, int) and limit > 0: filters.append('limit=%s' % limit) if marker is not None: filters.append('marker=%s' % marker) if sort_key is not None: filters.append('sort_key=%s' % sort_key) if sort_dir is not None: filters.append('sort_dir=%s' % sort_dir) if fields is not None: filters.append('fields=%s' % ','.join(fields)) if detail: filters.append('detail=True') return filters @contextlib.contextmanager def tempdir(*args, **kwargs): dirname = tempfile.mkdtemp(*args, **kwargs) try: yield dirname finally: shutil.rmtree(dirname) def make_configdrive(path): """Make the config drive file. :param path: The directory containing the config drive files. :returns: A gzipped and base64 encoded configdrive string. """ # Make sure path it's readable if not os.access(path, os.R_OK): raise exc.CommandError(_('The directory "%s" is not readable') % path) with tempfile.NamedTemporaryFile() as tmpfile: with tempfile.NamedTemporaryFile() as tmpzipfile: publisher = 'ironicclient-configdrive 0.1' # NOTE(toabctl): Luckily, genisoimage, mkisofs and xorrisofs # understand the same parameters which are currently used. cmds = ['genisoimage', 'mkisofs', 'xorrisofs'] for c in cmds: try: p = subprocess.Popen([c, '-o', tmpfile.name, '-ldots', '-allow-lowercase', '-allow-multidot', '-l', '-publisher', publisher, '-quiet', '-J', '-r', '-V', 'config-2', path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError as e: error = e else: error = None break if error: raise exc.CommandError( _('Error generating the config drive. Make sure the ' '"genisoimage", "mkisofs", or "xorriso" tool is ' 'installed. Error: %s') % error) stdout, stderr = p.communicate() if p.returncode != 0: raise exc.CommandError( _('Error generating the config drive.' 'Stdout: "%(stdout)s". Stderr: %(stderr)s') % {'stdout': stdout, 'stderr': stderr}) # Compress file tmpfile.seek(0) with gzip.GzipFile(fileobj=tmpzipfile, mode='wb') as gz_file: shutil.copyfileobj(tmpfile, gz_file) tmpzipfile.seek(0) return base64.b64encode(tmpzipfile.read()) def check_empty_arg(arg, arg_descriptor): if not arg.strip(): raise exc.CommandError(_('%(arg)s cannot be empty or only have blank' ' spaces') % {'arg': arg_descriptor}) def bool_argument_value(arg_name, bool_str, strict=True, default=False): """Returns the Boolean represented by bool_str. Returns the Boolean value for the argument named arg_name. The value is represented by the string bool_str. If the string is an invalid Boolean string: if strict is True, a CommandError exception is raised; otherwise the default value is returned. :param arg_name: The name of the argument :param bool_str: The string representing a Boolean value :param strict: Used if the string is invalid. If True, raises an exception. If False, returns the default value. :param default: The default value to return if the string is invalid and not strict :returns: the Boolean value represented by bool_str or the default value if bool_str is invalid and strict is False :raises CommandError: if bool_str is an invalid Boolean string """ try: val = strutils.bool_from_string(bool_str, strict, default) except ValueError as e: raise exc.CommandError(_("argument %(arg)s: %(err)s.") % {'arg': arg_name, 'err': e}) return val def check_for_invalid_fields(fields, valid_fields): """Check for invalid fields. :param fields: A list of fields specified by the user. :param valid_fields: A list of valid fields. :raises CommandError: If invalid fields were specified by the user. """ if not fields: return invalid_fields = set(fields) - set(valid_fields) if invalid_fields: raise exc.CommandError( _('Invalid field(s) requested: %(invalid)s. Valid fields ' 'are: %(valid)s.') % {'invalid': ', '.join(invalid_fields), 'valid': ', '.join(valid_fields)}) def get_from_stdin(info_desc): """Read information from stdin. :param info_desc: A string description of the desired information :raises: InvalidAttribute if there was a problem reading from stdin :returns: the string that was read from stdin """ try: info = sys.stdin.read().strip() except Exception as e: err = _("Cannot get %(desc)s from standard input. Error: %(err)s") raise exc.InvalidAttribute(err % {'desc': info_desc, 'err': e}) return info def handle_json_or_file_arg(json_arg): """Attempts to read JSON argument from file or string. :param json_arg: May be a file name containing the YAML or JSON, or a JSON string. :returns: A list or dictionary parsed from JSON. :raises: InvalidAttribute if the argument cannot be parsed. """ if os.path.isfile(json_arg): try: with open(json_arg, 'r') as f: return yaml.safe_load(f) except Exception as e: err = _("Cannot get JSON/YAML from file '%(file)s'. " "Error: %(err)s") % {'err': e, 'file': json_arg} raise exc.InvalidAttribute(err) try: json_arg = json.loads(json_arg) except ValueError as e: err = (_("Value '%(string)s' is not a file and cannot be parsed " "as JSON: '%(err)s'") % {'err': e, 'string': json_arg}) raise exc.InvalidAttribute(err) return json_arg def poll(timeout, poll_interval, poll_delay_function, timeout_message): if not isinstance(timeout, (int, float)) or timeout < 0: raise ValueError(_('Timeout must be a non-negative number')) threshold = time.time() + timeout poll_delay_function = (time.sleep if poll_delay_function is None else poll_delay_function) if not callable(poll_delay_function): raise TypeError(_('poll_delay_function must be callable')) count = 0 while not timeout or time.time() < threshold: yield count poll_delay_function(poll_interval) count += 1 raise exc.StateTransitionTimeout(timeout_message) def handle_json_arg(json_arg, info_desc): """Read a JSON argument from stdin, file or string. :param json_arg: May be a file name containing the JSON, a JSON string, or '-' indicating that the argument should be read from standard input. :param info_desc: A string description of the desired information :returns: A list or dictionary parsed from JSON. :raises: InvalidAttribute if the argument cannot be parsed. """ if json_arg == '-': json_arg = get_from_stdin(info_desc) if json_arg: json_arg = handle_json_or_file_arg(json_arg) return json_arg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/exc.py0000664000175000017500000000520700000000000021440 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common.apiclient import exceptions from ironicclient.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards # compatibility. InvalidEndpoint = exceptions.EndpointException CommunicationError = exceptions.ConnectionRefused HTTPBadRequest = exceptions.BadRequest HTTPInternalServerError = exceptions.InternalServerError HTTPNotFound = exceptions.NotFound HTTPServiceUnavailable = exceptions.ServiceUnavailable class AmbiguousAuthSystem(exceptions.ClientException): """Could not obtain token and endpoint using provided credentials.""" pass # Alias for backwards compatibility AmbigiousAuthSystem = AmbiguousAuthSystem class InvalidAttribute(exceptions.ClientException): pass class StateTransitionFailed(exceptions.ClientException): """Failed to reach a requested provision state.""" class StateTransitionTimeout(exceptions.ClientException): """Timed out while waiting for a requested provision state.""" def from_response(response, message=None, traceback=None, method=None, url=None): """Return an HttpError instance based on response from httplib/requests.""" error_body = {} if message: error_body['message'] = message if traceback: error_body['details'] = traceback if hasattr(response, 'status') and not hasattr(response, 'status_code'): # NOTE(akurilin): These modifications around response object give # ability to get all necessary information in method `from_response` # from common code, which expecting response object from `requests` # library instead of object from `httplib/httplib2` library. response.status_code = response.status response.headers = { 'Content-Type': response.getheader('content-type', "")} if hasattr(response, 'status_code'): # NOTE(jiangfei): These modifications allow SessionClient # to handle faultstring. response.json = lambda: {'error': error_body} return exceptions.from_response(response, method=method, url=url) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.248275 python-ironicclient-4.11.0/ironicclient/osc/0000775000175000017500000000000000000000000021067 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/__init__.py0000664000175000017500000000000000000000000023166 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/plugin.py0000664000175000017500000001162100000000000022740 0ustar00zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """OpenStackClient plugin for Bare Metal service.""" import argparse import logging from osc_lib import utils from ironicclient.common import http LOG = logging.getLogger(__name__) CLIENT_CLASS = 'ironicclient.v1.client.Client' API_VERSION_OPTION = 'os_baremetal_api_version' API_NAME = 'baremetal' # NOTE(TheJulia) Latest known version tracking has been moved # to the ironicclient/common/http.py file as the OSC committment # is latest known, and we should only store it in one location. LAST_KNOWN_API_VERSION = http.LAST_KNOWN_API_VERSION LATEST_VERSION = http.LATEST_VERSION API_VERSIONS = { '1.%d' % i: CLIENT_CLASS for i in range(1, LAST_KNOWN_API_VERSION + 1) } API_VERSIONS['1'] = CLIENT_CLASS # NOTE(dtantsur): flag to indicate that the requested version was "latest". # Due to how OSC works we cannot just add "latest" to the list of supported # versions - it breaks the major version detection. OS_BAREMETAL_API_LATEST = True def make_client(instance): """Returns a baremetal service client.""" requested_api_version = instance._api_version[API_NAME] baremetal_client_class = utils.get_client_class( API_NAME, requested_api_version, API_VERSIONS) LOG.debug('Instantiating baremetal client: %s', baremetal_client_class) LOG.debug('Baremetal API version: %s', requested_api_version if not OS_BAREMETAL_API_LATEST else "latest") if requested_api_version == '1': # NOTE(dtantsur): '1' means 'the latest v1 API version'. Since we don't # have other major versions, it's identical to 'latest'. requested_api_version = LATEST_VERSION allow_api_version_downgrade = True else: allow_api_version_downgrade = OS_BAREMETAL_API_LATEST client = baremetal_client_class( os_ironic_api_version=requested_api_version, # NOTE(dtantsur): enable re-negotiation of the latest version, if CLI # latest is too high for the server we're talking to. allow_api_version_downgrade=allow_api_version_downgrade, session=instance.session, region_name=instance._region_name, # NOTE(vdrok): This will be set as endpoint_override, and the Client # class will be able to do the version stripping if needed endpoint_override=instance.get_endpoint_for_service_type( API_NAME, interface=instance.interface, region_name=instance._region_name ) ) return client def build_option_parser(parser): """Hook to add global options.""" parser.add_argument( '--os-baremetal-api-version', metavar='', default=_get_environment_version("latest"), choices=sorted( API_VERSIONS, key=lambda k: [int(x) for x in k.split('.')]) + ['latest'], action=ReplaceLatestVersion, help='Bare metal API version, default="latest" (the maximum version ' 'supported by both the client and the server). ' '(Env: OS_BAREMETAL_API_VERSION)', ) return parser def _get_environment_version(default): global OS_BAREMETAL_API_LATEST env_value = utils.env('OS_BAREMETAL_API_VERSION') if not env_value: env_value = default if env_value == 'latest': env_value = LATEST_VERSION else: OS_BAREMETAL_API_LATEST = False return env_value class ReplaceLatestVersion(argparse.Action): """Replaces `latest` keyword by last known version. OSC cannot accept the literal "latest" as a supported API version as it breaks the major version detection (OSC tries to load configuration options from setuptools entrypoint openstack.baremetal.vlatest). This action replaces "latest" with the latest known version, and sets the global OS_BAREMETAL_API_LATEST flag appropriately. """ def __call__(self, parser, namespace, values, option_string=None): global OS_BAREMETAL_API_LATEST if values == 'latest': values = LATEST_VERSION # The default value of "True" may have been overridden due to # non-empty OS_BAREMETAL_API_VERSION env variable. If a user # explicitly requests "latest", we need to correct it. OS_BAREMETAL_API_LATEST = True else: OS_BAREMETAL_API_LATEST = False setattr(namespace, self.dest, values) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1645793348.252275 python-ironicclient-4.11.0/ironicclient/osc/v1/0000775000175000017500000000000000000000000021415 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/__init__.py0000664000175000017500000000000000000000000023514 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/baremetal_allocation.py0000664000175000017500000003352700000000000026142 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalAllocation(command.ShowOne): """Create a new baremetal allocation.""" log = logging.getLogger(__name__ + ".CreateBaremetalAllocation") def get_parser(self, prog_name): parser = super(CreateBaremetalAllocation, self).get_parser(prog_name) parser.add_argument( '--resource-class', dest='resource_class', help=_('Resource class to request.')) parser.add_argument( '--trait', action='append', dest='traits', help=_('A trait to request. Can be specified multiple times.')) parser.add_argument( '--candidate-node', action='append', dest='candidate_nodes', help=_('A candidate node for this allocation. Can be specified ' 'multiple times. If at least one is specified, only the ' 'provided candidate nodes are considered for the ' 'allocation.')) parser.add_argument( '--name', dest='name', help=_('Unique name of the allocation.')) parser.add_argument( '--uuid', dest='uuid', help=_('UUID of the allocation.')) parser.add_argument( '--owner', dest='owner', help=_('Owner of the allocation.')) parser.add_argument( '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) parser.add_argument( '--wait', type=int, dest='wait_timeout', default=None, metavar='', const=0, nargs='?', help=_("Wait for the new allocation to become active. An error " "is returned if allocation fails and --wait is used. " "Optionally takes a timeout value (in seconds). The " "default value is 0, meaning it will wait indefinitely.")) parser.add_argument( '--node', help=_("Backfill this allocation from the provided node that has " "already been deployed. Bypasses the normal allocation " "process.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal if not parsed_args.node and not parsed_args.resource_class: raise exc.ClientException( _('--resource-class is required except when --node is used')) field_list = ['name', 'uuid', 'extra', 'resource_class', 'traits', 'candidate_nodes', 'node', 'owner'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'extra') allocation = baremetal_client.allocation.create(**fields) if parsed_args.wait_timeout is not None: allocation = baremetal_client.allocation.wait( allocation.uuid, timeout=parsed_args.wait_timeout) data = dict([(f, getattr(allocation, f, '')) for f in res_fields.ALLOCATION_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalAllocation(command.ShowOne): """Show baremetal allocation details.""" log = logging.getLogger(__name__ + ".ShowBaremetalAllocation") def get_parser(self, prog_name): parser = super(ShowBaremetalAllocation, self).get_parser(prog_name) parser.add_argument( "allocation", metavar="", help=_("UUID or name of the allocation")) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.ALLOCATION_DETAILED_RESOURCE.fields, default=[], help=_("One or more allocation fields. Only these fields will be " "fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None allocation = baremetal_client.allocation.get( parsed_args.allocation, fields=fields)._info allocation.pop("links", None) return zip(*sorted(allocation.items())) class ListBaremetalAllocation(command.Lister): """List baremetal allocations.""" log = logging.getLogger(__name__ + ".ListBaremetalAllocation") def get_parser(self, prog_name): parser = super(ListBaremetalAllocation, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of allocations to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.')) parser.add_argument( '--marker', metavar='', help=_('Allocation UUID (for example, of the last allocation in ' 'the list from a previous request). Returns the list of ' 'allocations after this UUID.')) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified allocation fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.')) parser.add_argument( '--node', metavar='', help=_("Only list allocations of this node (name or UUID).")) parser.add_argument( '--resource-class', metavar='', help=_("Only list allocations with this resource class.")) parser.add_argument( '--state', metavar='', help=_("Only list allocations in this state.")) parser.add_argument( '--owner', metavar='', help=_("Only list allocations with this owner.")) # NOTE(dtantsur): the allocation API does not expose the 'detail' flag, # but some fields are inconvenient to display in a table, so we emulate # it on the client side. display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', default=False, help=_("Show detailed information about the allocations."), action='store_true') display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.ALLOCATION_DETAILED_RESOURCE.fields, help=_("One or more allocation fields. Only these fields will be " "fetched from the server. Can not be used when '--long' " "is specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker for field in ('node', 'resource_class', 'state', 'owner'): value = getattr(parsed_args, field) if value is not None: params[field] = value if parsed_args.long: columns = res_fields.ALLOCATION_DETAILED_RESOURCE.fields labels = res_fields.ALLOCATION_DETAILED_RESOURCE.labels elif parsed_args.fields: fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns else: columns = res_fields.ALLOCATION_RESOURCE.fields labels = res_fields.ALLOCATION_RESOURCE.labels self.log.debug("params(%s)", params) data = client.allocation.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns) for s in data)) class DeleteBaremetalAllocation(command.Command): """Unregister baremetal allocation(s).""" log = logging.getLogger(__name__ + ".DeleteBaremetalAllocation") def get_parser(self, prog_name): parser = super(DeleteBaremetalAllocation, self).get_parser(prog_name) parser.add_argument( "allocations", metavar="", nargs="+", help=_("Allocations(s) to delete (name or UUID).")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for allocation in parsed_args.allocations: try: baremetal_client.allocation.delete(allocation) print(_('Deleted allocation %s') % allocation) except exc.ClientException as e: failures.append(_("Failed to delete allocation " "%(allocation)s: %(error)s") % {'allocation': allocation, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class SetBaremetalAllocation(command.Command): """Set baremetal allocation properties.""" log = logging.getLogger(__name__ + ".SetBaremetalAllocation") def get_parser(self, prog_name): parser = super(SetBaremetalAllocation, self).get_parser(prog_name) parser.add_argument( "allocation", metavar="", help=_("Name or UUID of the allocation") ) parser.add_argument( "--name", metavar="", help=_("Set the name of the allocation") ) parser.add_argument( "--extra", metavar="", action="append", help=_("Extra property to set on this allocation " "(repeat option to set multiple extra properties)") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.name: properties.extend(utils.args_array_to_patch( 'add', ["name=%s" % parsed_args.name])) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ["extra/" + x for x in parsed_args.extra])) if properties: baremetal_client.allocation.update( parsed_args.allocation, properties) else: self.log.warning("Please specify what to set.") class UnsetBaremetalAllocation(command.Command): """Unset baremetal allocation properties.""" log = logging.getLogger(__name__ + ".UnsetBaremetalAllocation") def get_parser(self, prog_name): parser = super(UnsetBaremetalAllocation, self).get_parser( prog_name) parser.add_argument( "allocation", metavar="", help=_("Name or UUID of the allocation") ) parser.add_argument( "--name", action="store_true", default=False, help=_("Unset the name of the allocation") ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra property to unset on this baremetal allocation ' '(repeat option to unset multiple extra property).'), ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.name: properties.extend(utils.args_array_to_patch('remove', ['name'])) if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.allocation.update(parsed_args.allocation, properties) else: self.log.warning("Please specify what to unset.") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/baremetal_chassis.py0000664000175000017500000002557600000000000025457 0ustar00zuulzuul00000000000000# # Copyright 2016 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalChassis(command.ShowOne): """Create a new chassis.""" log = logging.getLogger(__name__ + ".CreateBaremetalChassis") def get_parser(self, prog_name): parser = super(CreateBaremetalChassis, self).get_parser(prog_name) parser.add_argument( '--description', dest='description', metavar='', help=_('Description for the chassis') ) parser.add_argument( '--extra', metavar='', action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.") ) parser.add_argument( '--uuid', metavar='', help=_("Unique UUID of the chassis") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal field_list = ['description', 'extra', 'uuid'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') chassis = baremetal_client.chassis.create(**fields)._info chassis.pop('links', None) chassis.pop('nodes', None) return self.dict2columns(chassis) class DeleteBaremetalChassis(command.Command): """Delete a chassis.""" log = logging.getLogger(__name__ + ".DeleteBaremetalChassis") def get_parser(self, prog_name): parser = super(DeleteBaremetalChassis, self).get_parser(prog_name) parser.add_argument( "chassis", metavar="", nargs="+", help=_("UUIDs of chassis to delete") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for chassis in parsed_args.chassis: try: baremetal_client.chassis.delete(chassis) print(_('Deleted chassis %s') % chassis) except exc.ClientException as e: failures.append(_("Failed to delete chassis %(chassis)s: " "%(error)s") % {'chassis': chassis, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class ListBaremetalChassis(command.Lister): """List the chassis.""" log = logging.getLogger(__name__ + ".ListBaremetalChassis") def get_parser(self, prog_name): parser = super(ListBaremetalChassis, self).get_parser(prog_name) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.CHASSIS_DETAILED_RESOURCE.fields, help=_("One or more chassis fields. Only these fields will be " "fetched from the server. Cannot be used when '--long' is " "specified.") ) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of chassis to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.') ) display_group.add_argument( '--long', default=False, action='store_true', help=_("Show detailed information about the chassis") ) parser.add_argument( '--marker', metavar='', help=_('Chassis UUID (for example, of the last chassis in the ' 'list from a previous request). Returns the list of ' 'chassis after this UUID.') ) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified chassis fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.CHASSIS_RESOURCE.fields labels = res_fields.CHASSIS_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.long: params['detail'] = parsed_args.long columns = res_fields.CHASSIS_DETAILED_RESOURCE.fields labels = res_fields.CHASSIS_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.chassis.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class SetBaremetalChassis(command.Command): """Set chassis properties.""" log = logging.getLogger(__name__ + ".SetBaremetalChassis") def get_parser(self, prog_name): parser = super(SetBaremetalChassis, self).get_parser(prog_name) parser.add_argument( 'chassis', metavar='', help=_("UUID of the chassis") ) parser.add_argument( "--description", metavar="", help=_("Set the description of the chassis") ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to set on this chassis ' '(repeat option to set multiple extras)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.description: description = ["description=%s" % parsed_args.description] properties.extend(utils.args_array_to_patch( 'add', description)) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.chassis.update(parsed_args.chassis, properties) else: self.log.warning("Please specify what to set.") class ShowBaremetalChassis(command.ShowOne): """Show chassis details.""" log = logging.getLogger(__name__ + ".ShowBaremetalChassis") def get_parser(self, prog_name): parser = super(ShowBaremetalChassis, self).get_parser(prog_name) parser.add_argument( "chassis", metavar="", help=_("UUID of the chassis") ) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.CHASSIS_DETAILED_RESOURCE.fields, default=[], help=_("One or more chassis fields. Only these fields will be " "fetched from the server.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None chassis = baremetal_client.chassis.get(parsed_args.chassis, fields=fields)._info chassis.pop("links", None) chassis.pop("nodes", None) return zip(*sorted(chassis.items())) class UnsetBaremetalChassis(command.Command): """Unset chassis properties.""" log = logging.getLogger(__name__ + ".UnsetBaremetalChassis") def get_parser(self, prog_name): parser = super(UnsetBaremetalChassis, self).get_parser(prog_name) parser.add_argument( 'chassis', metavar='', help=_("UUID of the chassis") ) parser.add_argument( '--description', action='store_true', default=False, help=_('Clear the chassis description') ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to unset on this chassis ' '(repeat option to unset multiple extras)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.description: properties.extend(utils.args_array_to_patch('remove', ['description'])) if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.chassis.update(parsed_args.chassis, properties) else: self.log.warning("Please specify what to unset.") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/baremetal_conductor.py0000775000175000017500000001255600000000000026017 0ustar00zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class ListBaremetalConductor(command.Lister): """List baremetal conductors""" log = logging.getLogger(__name__ + ".ListBaremetalNode") def get_parser(self, prog_name): parser = super(ListBaremetalConductor, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of conductors to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.') ) parser.add_argument( '--marker', metavar='', help=_('Hostname of the conductor (for example, of the last ' 'conductor in the list from a previous request). Returns ' 'the list of conductors after this conductor.') ) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified conductor fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.'), ) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', default=False, help=_("Show detailed information about the conductors."), action='store_true') display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.CONDUCTOR_DETAILED_RESOURCE.fields, help=_("One or more conductor fields. Only these fields will be " "fetched from the server. Can not be used when '--long' " "is specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.CONDUCTOR_RESOURCE.fields labels = res_fields.CONDUCTOR_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.long: params['detail'] = parsed_args.long columns = res_fields.CONDUCTOR_DETAILED_RESOURCE.fields labels = res_fields.CONDUCTOR_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.conductor.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class ShowBaremetalConductor(command.ShowOne): """Show baremetal conductor details""" log = logging.getLogger(__name__ + ".ShowBaremetalConductor") def get_parser(self, prog_name): parser = super(ShowBaremetalConductor, self).get_parser(prog_name) parser.add_argument( "conductor", metavar="", help=_("Hostname of the conductor")) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.CONDUCTOR_DETAILED_RESOURCE.fields, default=[], help=_("One or more conductor fields. Only these fields will be " "fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None conductor = baremetal_client.conductor.get( parsed_args.conductor, fields=fields)._info conductor.pop("links", None) return self.dict2columns(conductor) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/baremetal_create.py0000664000175000017500000000256600000000000025257 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.command import command from ironicclient.common.i18n import _ from ironicclient.v1 import create_resources class CreateBaremetal(command.Command): """Create resources from files""" log = logging.getLogger(__name__ + ".CreateBaremetal") def get_parser(self, prog_name): parser = super(CreateBaremetal, self).get_parser(prog_name) parser.add_argument( "resource_files", metavar="", nargs="+", help=_("File (.yaml or .json) containing descriptions of the " "resources to create. Can be specified multiple times.")) return parser def take_action(self, parsed_args): create_resources.create_resources(self.app.client_manager.baremetal, parsed_args.resource_files) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1645793293.0 python-ironicclient-4.11.0/ironicclient/osc/v1/baremetal_deploy_template.py0000664000175000017500000003015500000000000027176 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import json import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields _DEPLOY_STEPS_HELP = _( "The deploy steps. May be the path to a YAML file containing the deploy " "steps; OR '-', with the deploy steps being read from standard " "input; OR a JSON string. The value should be a list of deploy-step " "dictionaries; each dictionary should have keys 'interface', 'step', " "'args' and 'priority'.") class CreateBaremetalDeployTemplate(command.ShowOne): """Create a new deploy template""" log = logging.getLogger(__name__ + ".CreateBaremetalDeployTemplate") def get_parser(self, prog_name): parser = super(CreateBaremetalDeployTemplate, self).get_parser( prog_name) parser.add_argument( 'name', metavar='', help=_('Unique name for this deploy template. Must be a valid ' 'trait name') ) parser.add_argument( '--uuid', dest='uuid', metavar='', help=_('UUID of the deploy template.')) parser.add_argument( '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) parser.add_argument( '--steps', metavar="", required=True, help=_DEPLOY_STEPS_HELP ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal steps = utils.handle_json_arg(parsed_args.steps, 'deploy steps') field_list = ['name', 'uuid', 'extra'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'extra') template = baremetal_client.deploy_template.create(steps=steps, **fields) data = dict([(f, getattr(template, f, '')) for f in res_fields.DEPLOY_TEMPLATE_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalDeployTemplate(command.ShowOne): """Show baremetal deploy template details.""" log = logging.getLogger(__name__ + ".ShowBaremetalDeployTemplate") def get_parser(self, prog_name): parser = super(ShowBaremetalDeployTemplate, self).get_parser(prog_name) parser.add_argument( "template", metavar="