diff options
Diffstat (limited to 'ansible_collections/purestorage/fusion')
55 files changed, 811 insertions, 205 deletions
diff --git a/ansible_collections/purestorage/fusion/.github/workflows/ansible-lint.yaml b/ansible_collections/purestorage/fusion/.github/workflows/ansible-lint.yaml index 0b2102184..384c5ac93 100644 --- a/ansible_collections/purestorage/fusion/.github/workflows/ansible-lint.yaml +++ b/ansible_collections/purestorage/fusion/.github/workflows/ansible-lint.yaml @@ -1,5 +1,5 @@ -name: Ansible Lint # feel free to pick your own name -on: [push, pull_request] +name: Ansible Lint # feel free to pick your own name +"on": [push, pull_request] jobs: build: diff --git a/ansible_collections/purestorage/fusion/.github/workflows/black.yaml b/ansible_collections/purestorage/fusion/.github/workflows/black.yaml index 68061652a..10b16296c 100644 --- a/ansible_collections/purestorage/fusion/.github/workflows/black.yaml +++ b/ansible_collections/purestorage/fusion/.github/workflows/black.yaml @@ -1,6 +1,6 @@ name: Black -on: [push, pull_request] +"on": [push, pull_request] jobs: lint: diff --git a/ansible_collections/purestorage/fusion/.github/workflows/create-release.yaml b/ansible_collections/purestorage/fusion/.github/workflows/create-release.yaml index 25725c15d..68da05e4d 100644 --- a/ansible_collections/purestorage/fusion/.github/workflows/create-release.yaml +++ b/ansible_collections/purestorage/fusion/.github/workflows/create-release.yaml @@ -1,6 +1,6 @@ name: Release Collection -on: workflow_dispatch +"on": workflow_dispatch jobs: create_github_release: runs-on: ubuntu-latest @@ -23,7 +23,7 @@ jobs: if [[ "$response" == *"$RELEASE_VERSION"* ]]; then trap "exit 1" EXIT echo "Error: Tag $RELEASE_VERSION already exists" - exit 1 + exit 1 fi - name: Extract changelog diff --git a/ansible_collections/purestorage/fusion/.github/workflows/main.yml b/ansible_collections/purestorage/fusion/.github/workflows/main.yml index da0a69969..5c9a3914b 100644 --- a/ansible_collections/purestorage/fusion/.github/workflows/main.yml +++ b/ansible_collections/purestorage/fusion/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: Pure Storage Ansible CI -on: +"on": pull_request: push: schedule: @@ -13,36 +13,23 @@ jobs: strategy: matrix: ansible: - - stable-2.11 - - stable-2.12 - - stable-2.13 - stable-2.14 - stable-2.15 + - stable-2.16 - devel python-version: - - 3.8 - 3.9 - "3.10" - "3.11" exclude: - - python-version: "3.11" - ansible: stable-2.11 - - python-version: "3.11" - ansible: stable-2.12 - - python-version: "3.11" - ansible: stable-2.13 - - python-version: "3.10" - ansible: stable-2.11 - - python-version: 3.8 - ansible: stable-2.14 - - python-version: 3.8 - ansible: stable-2.15 - - python-version: 3.8 + - python-version: 3.9 + ansible: stable-2.16 + - python-version: 3.9 ansible: devel steps: - name: Check out code uses: actions/checkout@v3 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: diff --git a/ansible_collections/purestorage/fusion/.github/workflows/rh_automation_hub_token_keep_alive.yml b/ansible_collections/purestorage/fusion/.github/workflows/rh_automation_hub_token_keep_alive.yml new file mode 100644 index 000000000..f4e0a667b --- /dev/null +++ b/ansible_collections/purestorage/fusion/.github/workflows/rh_automation_hub_token_keep_alive.yml @@ -0,0 +1,19 @@ +--- +name: "Red Hat Automation Hub - Keep token alive" +# The SSO token to upload content to Automation Hub must be accessed once every 30 days or it will be turned off + +"on": + schedule: + - cron: '0 12 1,15 * *' # run 12pm on the 1st and 15th of the month + +jobs: + keep_rh_sso_token_alive: + runs-on: "ubuntu-latest" + steps: + - name: "Run curl command" + run: | + curl ${{ secrets.RH_AUTOMATION_HUB_URL }} \ + -d grant_type=refresh_token \ + -d client_id="cloud-services" \ + -d refresh_token="${{ secrets.RH_AUTOMATION_HUB_TOKEN }}" \ + --fail --silent --show-error --output /dev/null diff --git a/ansible_collections/purestorage/fusion/.github/workflows/stale.yml b/ansible_collections/purestorage/fusion/.github/workflows/stale.yml index 7bbc0505b..ee7c9796e 100644 --- a/ansible_collections/purestorage/fusion/.github/workflows/stale.yml +++ b/ansible_collections/purestorage/fusion/.github/workflows/stale.yml @@ -1,6 +1,6 @@ name: Mark stale issues and pull requests -on: +"on": schedule: - cron: "0 0 * * *" diff --git a/ansible_collections/purestorage/fusion/CHANGELOG.rst b/ansible_collections/purestorage/fusion/CHANGELOG.rst index b4d9bd6ae..b6a0f071a 100644 --- a/ansible_collections/purestorage/fusion/CHANGELOG.rst +++ b/ansible_collections/purestorage/fusion/CHANGELOG.rst @@ -5,6 +5,29 @@ Purestorage.Fusion Release Notes .. contents:: Topics +v1.6.1 +====== + +Minor Changes +------------- + +- fusion_volume - Allow creating a new volume from already existing volume or volume snapshot + +v1.6.0 +====== + +Minor Changes +------------- + +- all modules - return resource's id parameter on update and create. +- fusion_array - added `apartment_id` argument, which can be used when creating an array. +- fusion_pg - introduced `destroy_snapshots_on_delete` which, if set to true, ensures that before deleting placement group, snapshots within the placement group will be deleted. +- fusion_pp - 'local_rpo' duration parsing documented, 'local_retention' minimum value fixed +- fusion_pp - Allow leading zeros in duration strings +- fusion_pp - Change the minimum value of the protection policy local retention from 1 to 10 +- fusion_pp - introduced `destroy_snapshots_on_delete` which, if set to true, ensures that before deleting protection policy, snapshots within the protection policy will be deleted. +- fusion_volume - Allow creating a new volume from already existing volume or volume snapshot + v1.5.0 ====== diff --git a/ansible_collections/purestorage/fusion/FILES.json b/ansible_collections/purestorage/fusion/FILES.json index b3f73b7e0..ad0452035 100644 --- a/ansible_collections/purestorage/fusion/FILES.json +++ b/ansible_collections/purestorage/fusion/FILES.json @@ -15,17 +15,24 @@ "format": 1 }, { + "name": "changelogs/fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { "name": "changelogs/changelog.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b568495166ca2ef38576e62cc6f1eb2d1f4caa988b020112e14650d37510dd83", + "chksum_sha256": "17ac55399ed69cbac46280c27dde9825e556a3b5214ff7defef12cc1dbbae598", "format": 1 }, { "name": "changelogs/.plugin-cache.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "492bf617d0924a14708a862efd096e1a032e1a1243f25e2287e44a6e072e2f1a", + "chksum_sha256": "64d8cc09b182d4991facb3abb8835c821b4e0cb72d6dd687ddbe16402b6209cc", "format": 1 }, { @@ -347,7 +354,7 @@ "name": "tests/unit/modules/test_fusion_az.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "75be72264bf7d95ddc73d72c4763b6e877a05feaab2f6d9b91a55448bb77af51", + "chksum_sha256": "7d6b7a4a5a233ee47c6788d370bf1c6a6da5adf9b758b43c6d5557ba87b4dc58", "format": 1 }, { @@ -368,7 +375,7 @@ "name": "tests/unit/mocks/operation_mock.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "aaa5ad3b4a9bcd10a95947af5f06ec4153512927b56d94f4d442da6007d43c7b", + "chksum_sha256": "373ea55faf5262f157724aaf6d1ca31b963415ad1b180b2fa7833983acd1d8f2", "format": 1 }, { @@ -417,7 +424,7 @@ "name": "tests/unit/module_utils/test_parsing.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "58c8e7b81680984e3e606cc56210aa8afb93c020939f1d3d585b5cf7de61c513", + "chksum_sha256": "a7efaf296b085c6ffbf5174218e575491dcd850f74a159068d0004222d6fade6", "format": 1 }, { @@ -438,14 +445,14 @@ "name": "tests/functional/test_fusion_region.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8d108a21480c4cb9c9d2810e809ea876173b3d43621f417c0957c77d21f33f76", + "chksum_sha256": "5ba0b74a3580885a0e3dc5693adb85c1d863fe664588275c175131d59122b7f9", "format": 1 }, { "name": "tests/functional/test_fusion_ss.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "676bba88edd7f73957c605356d31f4bd61cd144d354280c373beb3689196d5cd", + "chksum_sha256": "6b44ef093fcfed0897a14395095185ac6bc1dd6a5f9b153c0e31857734a9a10e", "format": 1 }, { @@ -459,56 +466,56 @@ "name": "tests/functional/test_fusion_volume.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8b6a8f18e610fcd4f2aea719e5b51ff58ef6f6b06412afd98309255ccab7f8a4", + "chksum_sha256": "d307a55f86ea7683542e58d1416fe8d005d3dfcf65b84c8bd6b0f5c914e2aadf", "format": 1 }, { "name": "tests/functional/test_fusion_ts.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "dc844fc260f337525396d47da3e012fbb2f1f5188a96c3d1071515bdac879583", + "chksum_sha256": "5dadaf161e2daf306ef5d0eb72dd8e53db398a630f82b6d511fab4e836991489", "format": 1 }, { "name": "tests/functional/test_fusion_pg.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "4da9f7a334491933d40fe8d32fbae767393f84d744537a7d03a57d84a1693b38", + "chksum_sha256": "8df05411ef3cd13bd9f7436df88b7bb4c0611a3db23988435b5cc39e9546a967", "format": 1 }, { "name": "tests/functional/test_fusion_nig.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f20b2ab1eed1bd182d68a00198537f960e9c7e844cfb8df3c99922e98e2365c1", + "chksum_sha256": "a06addf4b81f91e57c7b2413da2a39279b3a794fecab23c3bb2a4609c22873f1", "format": 1 }, { "name": "tests/functional/test_fusion_se.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "79d30463a37430a6a697778bb58fe2ced187672ec74ddae6b98f191069931b04", + "chksum_sha256": "ad77974f2c4e7ea6ee3681c57ccaf235b2401e83d299495c862a717f6f2b7199", "format": 1 }, { "name": "tests/functional/test_fusion_az.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d6b7e24d40c1268b1ce0de3557210fbd89e97441dcd384522263f5982a5922b5", + "chksum_sha256": "39d40d8fee3e346a8dc7db8becf15c5149fbf13d1af6e29b2325a6dc9f8e9624", "format": 1 }, { "name": "tests/functional/test_fusion_info.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "05d60add74e73360eefd8679e808c2c5c5c774726a15c29d923dd077099e9537", + "chksum_sha256": "417845afcefefa6a2e739be86ed8ef7474b43409d44df3079bc4c537ff0821df", "format": 1 }, { "name": "tests/functional/test_fusion_hap.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0a8ffe64ef5a561e2eb102f58b20f62c99c8a79022be63976f6e8c19608178ab", + "chksum_sha256": "b7f1abe557c37b29f2a9270636f8b81745d72281141caac0fb0931a79dd391b4", "format": 1 }, { @@ -529,56 +536,56 @@ "name": "tests/functional/test_fusion_sc.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "cf1794f2f91b496adc54131be84e1ea81263ccf603cf648fecd0a52c7a1da467", + "chksum_sha256": "b3d781841cc7f3d2956f48dffd0e6ef35a0121841a1a0ac6915068c60ed5bda2", "format": 1 }, { "name": "tests/functional/test_fusion_ra.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "718b4be5026d83e2fe3fefe6838637efce66d9880c635a80603844266b3e926c", + "chksum_sha256": "36206507be9b65d09a4e01ff75740f6507f5a987180dde6f2d720ea76abc9b9b", "format": 1 }, { "name": "tests/functional/test_fusion_tenant.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b5c6413737db89d121c98f7798b36bb736b367fb6c2ee1651645c742822f9b66", + "chksum_sha256": "59d4786a7dfb78f72ebca838b7dbfda7444a861d7113b50ddebffd82bea56ade", "format": 1 }, { "name": "tests/functional/utils.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d6e339b28c2b3ed78108244bde3950424b3acc81a6a3b9c8cd7b32a53fbd5ba9", + "chksum_sha256": "e3d1d3cbd790a64d783f52914e52158e339a7c7eb86c6cb63546d72820e6d6c5", "format": 1 }, { "name": "tests/functional/test_fusion_api_client.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "44f1df7dfe3c53b30ae7c2c2fd2873b651f2306bba67a26310a8c2d86c53f04e", + "chksum_sha256": "5010777699d6259c58c38e7e51c84baa554b484736516ac180f3b1d3b5f844f8", "format": 1 }, { "name": "tests/functional/test_fusion_array.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "910cd4c0859534f5de3b8cb743c9f549b93f25c0f18399158adff63b933a8110", + "chksum_sha256": "ca4057d295d239c6c6e6afb7e701f9d337fb44a2dbaf29bb5805cbed80bbe93e", "format": 1 }, { "name": "tests/functional/test_fusion_pp.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3578756616fff28885b379a93221c3dfe8d083a9d685bd8b16878b7f9bf045c9", + "chksum_sha256": "c45337495b95f164eed6e3e6d14c038246d042f16315aea494276380f8afa370", "format": 1 }, { "name": "tests/helpers.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "74d50a53434e0ca164aa41ea5f272755e9b6ad0dc105d3eec53f62d3e188034c", + "chksum_sha256": "24f55093b6e7486c6c6c6a9cf57875ec69496ac23253f1c5a7f79c870c00d38a", "format": 1 }, { @@ -841,6 +848,20 @@ "format": 1 }, { + "name": "test", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "test/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a009a349eaaf78c93ff56072d2ef171937bdb884e4976592ab5aaa9c68e1044", + "format": 1 + }, + { "name": "plugins", "ftype": "dir", "chksum_type": null, @@ -858,42 +879,42 @@ "name": "plugins/modules/fusion_ts.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "91e740ffbf27ab279cc6fbd07b5a59e92de941a4e88f437040eae89c1b8f1f3b", + "chksum_sha256": "6d4c3f141409f2b1a5128912dbfe14341e17f6a9e5c20d14f3a5e82b75da4462", "format": 1 }, { "name": "plugins/modules/fusion_nig.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "59cd2a72c5544ebf83ff6fe239f623ec03b4de84efb7cb08fdf4b4159544bc2c", + "chksum_sha256": "c220a2793545a832096d78d5d926a7a3037a828a0f2e042601e7e00afa29471b", "format": 1 }, { "name": "plugins/modules/fusion_api_client.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5b92104038365e11b958a75a521a843c7b599e950e1d7815ff40a01a519dfff5", + "chksum_sha256": "a0e35d215e9cce9b2f5e64e8661a12414ddea48df96a24e0f7b7ae9071aad43e", "format": 1 }, { "name": "plugins/modules/fusion_ni.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5edf635cb12288af965649f3941bac2f1eea781c2e23793ac40988faedd34735", + "chksum_sha256": "148fdf12357a1b56c30343bb2a46b3d4cc2ff4f179f667c764d64101abc5057b", "format": 1 }, { "name": "plugins/modules/fusion_ss.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c278ef52dbd80a2c143b56ace8f31ebcca5ae76426bc7e38bea3e7e66a1a5742", + "chksum_sha256": "9ffdfd9f3ae2fa8df1a37b52370cc49e265801d25efcc17eb65649d92f72d4d9", "format": 1 }, { "name": "plugins/modules/fusion_pp.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "29b9019589464b7650892f84ebe112d03f38e03e8c804d6ce35401f85e31603f", + "chksum_sha256": "63543885c2adc09b3f829f0600774cf2ca098010ee2c9b58ce359ed383d7a780", "format": 1 }, { @@ -907,42 +928,42 @@ "name": "plugins/modules/fusion_array.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0edaabce3e589722d0dd299f7f81d66811351e678046068fae179ad3f331fa4e", + "chksum_sha256": "6baebe9c43933cc7bc0c43455a4019eb88805b0f11671a2331cc86bb8775d438", "format": 1 }, { "name": "plugins/modules/fusion_az.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f0e9ea0a969913323e917d5b7d631c38e33b3e55a3b641cf553c8ea01228f0a5", + "chksum_sha256": "ecaf8c75e0941895ba3b8f349c25049d7e2d6eb2468a609926d57c2d865ba264", "format": 1 }, { "name": "plugins/modules/fusion_pg.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3c03eb5a59d749a954fe09d4c2a57ec36d30f9bdd51565c8e1e3d3e293d2bbc5", + "chksum_sha256": "819e0298797184fd0a5d54fdb6dcebc9e8fad118a353c217311eddd7608ac2d4", "format": 1 }, { "name": "plugins/modules/fusion_volume.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e2b6a4837e1abc3efc2fa88707cfa80f618b800bccdad6bd5a5ac3f60ba77d14", + "chksum_sha256": "ccf9e89f5c10eac9d992ac0740c082ebaaedf7d8d9ded38322223cc57a2f98d5", "format": 1 }, { "name": "plugins/modules/fusion_tenant.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "03823b7283e0de940ee3e95bf5645595e4330759ad7dd18f0411c319774ec173", + "chksum_sha256": "18887fdb6f7a44b647db0e25f726d605e00ded8396bc9841b29ddec1b56e101e", "format": 1 }, { "name": "plugins/modules/fusion_se.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "355892de73b5d265e1e57e8ff31b3dd0775c04a191ded999131ebbfdbbcd7231", + "chksum_sha256": "9c77f1b938823461f586c66949527a5377dff942a7ec1024d729f190b2e127fa", "format": 1 }, { @@ -956,21 +977,21 @@ "name": "plugins/modules/fusion_ra.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "4a1bd14fe1038fbf09d4196143f4ac262ef7627ee550ea2efbaeceaa3e0a6176", + "chksum_sha256": "20c1a2bbde557ebc6b03453552c2a2f718f11271ac96d75e7beb43c04095157c", "format": 1 }, { "name": "plugins/modules/fusion_sc.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7302c71a969dbbc3fb636455ee16ef807a3e2c212d307c305b86504b2b42603c", + "chksum_sha256": "12711136fb3fa243fd65f15df009f9f0b68d5ee9fef9b11f5f7d7de38526afdb", "format": 1 }, { "name": "plugins/modules/fusion_region.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d6cb89588cca8681cfc12651ace18375eba88d2aaaacf2ece2d7652d9821fde9", + "chksum_sha256": "1bb063fced01a15617c99e4be32a95265e1678c75fc892ea3e1b9e93f3f683fb", "format": 1 }, { @@ -984,7 +1005,7 @@ "name": "plugins/modules/fusion_hap.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ffc4d041a552ac1d45ab51868428f9281829d2f345581eef8f379b1692e50a1a", + "chksum_sha256": "0b3e80b8cc3d42fc06b657d01258f24a805b6027ccfdc4286ddd66ddce554ea1", "format": 1 }, { @@ -1019,7 +1040,7 @@ "name": "plugins/module_utils/prerequisites.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "589f5ad7eed9dfe57263a9d3ec7dd6b179da0406aa2a6706ec056f3ab60af5cd", + "chksum_sha256": "d4c21413eceda5c98229edd0999d45c3b87554fae9eeb096322d03dab91ac870", "format": 1 }, { @@ -1033,14 +1054,14 @@ "name": "plugins/module_utils/errors.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "fa7c577ce38810b137980e87e6e5b87e95fb43e101d02652df7cbb434f630699", + "chksum_sha256": "8b39a68c54dd07e3061824d2ad60ba4f9cebbf7b3c020283ceb3859e4e0e28a9", "format": 1 }, { "name": "plugins/module_utils/parsing.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "efe7b474e24d7fa53dc134f7fd1e4062542a22d5ea9f8b16715ab8a353d1d953", + "chksum_sha256": "61e0fac0fa4ff6bbd1eb7f9dd1ba1b6822965f5f7c7691537222d3af37e725c2", "format": 1 }, { @@ -1051,6 +1072,13 @@ "format": 1 }, { + "name": "plugins/module_utils/snapshots.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7571aba639702400e659241f9d8f61dd72f6ef3119b0af5296d7522dbd5125f", + "format": 1 + }, + { "name": "plugins/module_utils/startup.py", "ftype": "file", "chksum_type": "sha256", @@ -1124,35 +1152,42 @@ "name": ".github/workflows/ansible-lint.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "4c85688d98b71e3a6594530a362cd5d2cf83842ceaccd0e0fc76e233777c1cef", + "chksum_sha256": "62dbc43cafdab8da066ba0d86a08924e433f8b2919cdef935c116c5962d3a572", + "format": 1 + }, + { + "name": ".github/workflows/rh_automation_hub_token_keep_alive.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3c7f513c85853a9f152635f5fc9f4f8a1e621cc8b2a40c405d9efc69830800f6", "format": 1 }, { "name": ".github/workflows/stale.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0bdef4889afabcd627fc30711a0809c7468b8c9e64cbcebe1334f794a41e7bd9", + "chksum_sha256": "544ccc9f17e16d9087802e3dcec69741e6ff79e31cf7302947ce2c08126ce1d4", "format": 1 }, { "name": ".github/workflows/black.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c62a1a4fcc1e00f3e8f295863e304db520124bfd3e9b0c2cccd6d78343b679c5", + "chksum_sha256": "b82c6a8af5e7c7d2113fecafa178bf6df94de434d4dc6e2ed6c3bc695da74f41", "format": 1 }, { "name": ".github/workflows/create-release.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "43ea888cb2b22ddc86ea989f75967accaff0065cc43c39a0043ba6cf2f424378", + "chksum_sha256": "12ebf07984e4908dd2a6bed45d8bf38641bf3f264fe30ead9ce2849e6fcc8eb5", "format": 1 }, { "name": ".github/workflows/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0c8c2578e81d44e4a9611c57a59c6fbc7dd947ff149a169ea65f497484d6d4a4", + "chksum_sha256": "60e50d69898144d914ad2af759c744bd3ec8ccc78141cbeb13b850f283e20653", "format": 1 }, { @@ -1166,7 +1201,7 @@ "name": "README.md", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a753d4c6dc5cdd493fd60f147cf68f644ec6f301b895fc249093914db1cf3ab1", + "chksum_sha256": "2958e9b57938d749df6845d5d1a7e65c499990637af1056923ed6efa22c5684e", "format": 1 }, { @@ -1180,7 +1215,7 @@ "name": "meta/runtime.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "9f829699b200db8a8282ce6f44d6ae28a2e3377e0e611b0d327db64b0cbba321", + "chksum_sha256": "359c08cf506ebfd67477b25cc2f4763a1495f398cfb3cc9dd2a29595dce990db", "format": 1 }, { @@ -1201,7 +1236,7 @@ "name": "CHANGELOG.rst", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "28eab01a890a0719cf1908791d9575a4d47014547796bb077f44702dbbc5632a", + "chksum_sha256": "9c8780730bed93a88dcaafd561c2a40c06cc7add04279bce943a6e2a7a2f8778", "format": 1 } ], diff --git a/ansible_collections/purestorage/fusion/MANIFEST.json b/ansible_collections/purestorage/fusion/MANIFEST.json index 4fe3bc8b5..877ab80f8 100644 --- a/ansible_collections/purestorage/fusion/MANIFEST.json +++ b/ansible_collections/purestorage/fusion/MANIFEST.json @@ -2,7 +2,7 @@ "collection_info": { "namespace": "purestorage", "name": "fusion", - "version": "1.5.0", + "version": "1.6.1", "authors": [ "Pure Storage Ansible Team <pure-ansible-team@purestorage.com>" ], @@ -27,7 +27,7 @@ "name": "FILES.json", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3e406206ea2f67e0a9846219a9d5d2813aef76437e1b05d12d341aded53cfd13", + "chksum_sha256": "fcdf40d7eabc65ac35369e36b6ed40579ce799ee0ea94caed4b7ab06c0efd0b5", "format": 1 }, "format": 1 diff --git a/ansible_collections/purestorage/fusion/README.md b/ansible_collections/purestorage/fusion/README.md index b2a36de10..0bb22423d 100644 --- a/ansible_collections/purestorage/fusion/README.md +++ b/ansible_collections/purestorage/fusion/README.md @@ -4,14 +4,20 @@ <img src="https://github.com/Pure-Storage-Ansible/Fusion-Collection/workflows/Pure%20Storage%20Ansible%20CI/badge.svg"> <a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg"></a> +# DEPRECATION NOTICE + +The Pure Storage Fusion Ansible Collection is no longer being developed and is being deprecated. + +No further development work will be performed on this repo and the repo will be archived. + # Pure Storage Fusion Collection -The Pure Storage Fusion collection consists of the latest versions of the Fusion modules. +The Pure Storage Fusion collection consists of the latest versions of the Fusion v1 modules. ## Requirements -- ansible-core >= 2.11 -- Python >= 3.8 +- ansible-core >= 2.14.0 +- Python >= 3.9 - Authorized API Application ID for Pure Storage Pure1 and associated Private Key - Refer to Pure Storage documentation on how to create these. - purefusion >= 1.0.4 diff --git a/ansible_collections/purestorage/fusion/changelogs/.plugin-cache.yaml b/ansible_collections/purestorage/fusion/changelogs/.plugin-cache.yaml index 23a38bf01..190d97ce5 100644 --- a/ansible_collections/purestorage/fusion/changelogs/.plugin-cache.yaml +++ b/ansible_collections/purestorage/fusion/changelogs/.plugin-cache.yaml @@ -111,4 +111,4 @@ plugins: strategy: {} test: {} vars: {} -version: 1.5.0 +version: 1.6.1 diff --git a/ansible_collections/purestorage/fusion/changelogs/changelog.yaml b/ansible_collections/purestorage/fusion/changelogs/changelog.yaml index 82ef323c8..c5d3f432d 100644 --- a/ansible_collections/purestorage/fusion/changelogs/changelog.yaml +++ b/ansible_collections/purestorage/fusion/changelogs/changelog.yaml @@ -343,3 +343,39 @@ releases: - 3289_functests_pp_pg_ra.yml - 99_update_protection_policy_retention_description.yaml release_date: '2023-05-31' + 1.6.0: + changes: + minor_changes: + - all modules - return resource's id parameter on update and create. + - fusion_array - added `apartment_id` argument, which can be used when creating + an array. + - fusion_pg - introduced `destroy_snapshots_on_delete` which, if set to true, + ensures that before deleting placement group, snapshots within the placement + group will be deleted. + - fusion_pp - 'local_rpo' duration parsing documented, 'local_retention' minimum + value fixed + - fusion_pp - Allow leading zeros in duration strings + - fusion_pp - Change the minimum value of the protection policy local retention + from 1 to 10 + - fusion_pp - introduced `destroy_snapshots_on_delete` which, if set to true, + ensures that before deleting protection policy, snapshots within the protection + policy will be deleted. + - fusion_volume - Allow creating a new volume from already existing volume or + volume snapshot + fragments: + - 148_add_apartment_id_to_fusion_array.yml + - 151_create_volume_using_existing_volume_or_snapshot.yaml + - 152_fix_rpo_local_retention_doc.yaml + - 154_add_destroy_snapshots_on_delete_to_pp_and_pg.yml + - 156_allow_leading_zeros.yaml + - 159_fix_protection_policy_local_retention_validation.yaml + - 160_add_id_on_exit.yml + release_date: '2023-07-31' + 1.6.1: + changes: + minor_changes: + - fusion_volume - Allow creating a new volume from already existing volume or + volume snapshot + fragments: + - 151_create_volume_using_existing_volume_or_snapshot.yaml + release_date: '2024-02-08' diff --git a/ansible_collections/purestorage/fusion/meta/runtime.yml b/ansible_collections/purestorage/fusion/meta/runtime.yml index 1812440b2..6af15681b 100644 --- a/ansible_collections/purestorage/fusion/meta/runtime.yml +++ b/ansible_collections/purestorage/fusion/meta/runtime.yml @@ -1,5 +1,5 @@ --- -requires_ansible: ">=2.11.0" +requires_ansible: ">=2.14.0" plugin_routing: modules: fusion_tn: diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py b/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py index 0edf364cf..f3d574edc 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py @@ -162,7 +162,7 @@ def format_failed_fusion_operation_exception(exception): if not code: code = error.http_code operation_name = op.request_type - except Exception as e: + except Exception: pass output = "" diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py b/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py index a2cd75245..1bcb8b812 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py @@ -11,7 +11,7 @@ __metaclass__ = type METRIC_SUFFIXES = ["K", "M", "G", "T", "P"] duration_pattern = re.compile( - r"^((?P<Y>[1-9]\d*)Y)?((?P<W>[1-9]\d*)W)?((?P<D>[1-9]\d*)D)?(((?P<H>[1-9]\d*)H)?((?P<M>[1-9]\d*)M)?)?$" + r"^((?P<Y>\d+)Y)?((?P<W>\d+)W)?((?P<D>\d+)D)?(((?P<H>\d+)H)?((?P<M>\d+)M)?)?$" ) duration_transformation = { "Y": 365 * 24 * 60, diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py b/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py index a4edaf341..db00a9c6f 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py @@ -136,7 +136,7 @@ def _check_import(ansible_module, module, package=None, version_requirements=Non :param version_requirements: a string, version requirements for 'package' """ try: - mod = importlib.import_module(module) + importlib.import_module(module) except ImportError: ansible_module.fail_json( msg="Error: Python package '{0}' required and missing".format(module) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py b/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py new file mode 100644 index 000000000..ed34c1c0e --- /dev/null +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +try: + import fusion as purefusion +except ImportError: + pass + +from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( + await_operation, +) + + +def delete_snapshot(fusion, snap, snapshots_api): + patch = purefusion.SnapshotPatch(destroyed=purefusion.NullableBoolean(True)) + op = snapshots_api.update_snapshot( + body=patch, + tenant_name=snap.tenant.name, + tenant_space_name=snap.tenant_space.name, + snapshot_name=snap.name, + ) + await_operation(fusion, op) + op = snapshots_api.delete_snapshot( + tenant_name=snap.tenant.name, + tenant_space_name=snap.tenant_space.name, + snapshot_name=snap.name, + ) + await_operation(fusion, op) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py index 39860449d..42254338f 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py @@ -99,14 +99,15 @@ def create_client(module, fusion): id_api_instance = purefusion.IdentityManagerApi(fusion) changed = True + id = None if not module.check_mode: client = purefusion.APIClientPost( public_key=module.params["public_key"], display_name=module.params["name"], ) - id_api_instance.create_api_client(client) - - module.exit_json(changed=changed) + res = id_api_instance.create_api_client(client) + id = res.id + module.exit_json(changed=changed, id=id) def main(): @@ -129,8 +130,8 @@ def main(): create_client(module, fusion) elif client_id is not None and state == "absent": delete_client(module, fusion, client_id) - else: - module.exit_json(changed=False) + if client_id is not None: + module.exit_json(changed=False, id=client_id) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py index f7933eabe..ec94d616f 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py @@ -60,6 +60,10 @@ options: description: - Appliance ID of the array. type: str + apartment_id: + description: + - The Apartment ID of the Array. + type: str maintenance_mode: description: - "Switch the array into maintenance mode or back. @@ -123,6 +127,7 @@ def create_array(module, fusion): """Create Array""" array_api_instance = purefusion.ArraysApi(fusion) + id = None if not module.check_mode: if not module.params["display_name"]: @@ -135,14 +140,17 @@ def create_array(module, fusion): host_name=module.params["host_name"], name=module.params["name"], appliance_id=module.params["appliance_id"], + apartment_id=module.params["apartment_id"], ) res = array_api_instance.create_array( array, availability_zone_name=module.params["availability_zone"], region_name=module.params["region"], ) - await_operation(fusion, res) - return True + res_op = await_operation(fusion, res) + id = res_op.result.resource.id + + return True, id def update_array(module, fusion): @@ -222,6 +230,7 @@ def main(): availability_zone=dict(type="str", required=True, aliases=["az"]), display_name=dict(type="str"), region=dict(type="str", required=True), + apartment_id=dict(type="str"), appliance_id=dict(type="str"), host_name=dict(type="str"), hardware_type=dict( @@ -246,17 +255,24 @@ def main(): array = get_array(module, fusion) changed = False + id = None + if array is not None: + id = array.id + if not array and state == "present": module.fail_on_missing_params(["hardware_type", "host_name", "appliance_id"]) - changed = create_array(module, fusion) | update_array( + changed, id = create_array(module, fusion) + update_array( module, fusion ) # update is run to set properties which cannot be set on creation and instead use defaults elif array and state == "present": - changed = changed | update_array(module, fusion) + changed = update_array(module, fusion) elif array and state == "absent": changed = changed | delete_array(module, fusion) - else: - module.exit_json(changed=False) + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py index 02647d397..b4a493861 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py @@ -112,6 +112,7 @@ def create_az(module, fusion): az_api_instance = purefusion.AvailabilityZonesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -125,9 +126,10 @@ def create_az(module, fusion): op = az_api_instance.create_availability_zone( azone, region_name=module.params["region"] ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def main(): @@ -152,8 +154,6 @@ def main(): create_az(module, fusion) elif azone and state == "absent": delete_az(module, fusion) - else: - module.exit_json(changed=False) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py index 3f45ea2dd..c4df0af49 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py @@ -170,8 +170,10 @@ def create_hap(module, fusion): display_name=display_name, ) ) - await_operation(fusion, op) - module.exit_json(changed=changed) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + + module.exit_json(changed=changed, id=id) def delete_hap(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py index 6816ed841..82c896fac 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py @@ -162,7 +162,7 @@ def update_ni(module, fusion, ni): ), ) patches.append(patch) - + id = None if not module.check_mode: for patch in patches: op = ni_api_instance.update_network_interface( @@ -172,11 +172,12 @@ def update_ni(module, fusion, ni): array_name=module.params["array"], net_intf_name=module.params["name"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py index d6056fd5a..d40b813b9 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py @@ -146,6 +146,7 @@ def create_nig(module, fusion): ): module.fail_json(msg="`gateway` must be an address in subnet `prefix`") + id = None if not module.check_mode: display_name = module.params["display_name"] or module.params["name"] if module.params["group_type"] == "eth": @@ -171,13 +172,14 @@ def create_nig(module, fusion): availability_zone_name=module.params["availability_zone"], region_name=module.params["region"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id changed = True else: # to prevent future unintended error module.warn(f"group_type={module.params['group_type']} is not implemented") - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_nig(module, fusion): @@ -220,7 +222,7 @@ def update_nig(module, fusion, nig): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=nig.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py index 57843d896..6d6f0eb94 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py @@ -36,6 +36,11 @@ options: type: str default: present choices: [ absent, present ] + destroy_snapshots_on_delete: + description: + - "Before deleting placement group, snapshots within the placement group will be deleted." + - "If `false` then any snapshots will need to be deleted as a separate step before removing the placement group." + type: bool tenant: description: - The name of the tenant. @@ -116,6 +121,9 @@ from ansible_collections.purestorage.fusion.plugins.module_utils.startup import from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( await_operation, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( + delete_snapshot, +) def get_pg(module, fusion): @@ -153,9 +161,10 @@ def create_pg(module, fusion): tenant_name=module.params["tenant"], tenant_space_name=module.params["tenant_space"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - return True + return True, id def update_display_name(module, fusion, patches, pg): @@ -213,6 +222,16 @@ def delete_pg(module, fusion): """Delete Placement Group""" pg_api_instance = purefusion.PlacementGroupsApi(fusion) if not module.check_mode: + if module.params["destroy_snapshots_on_delete"]: + snapshots_api = purefusion.SnapshotsApi(fusion) + snapshots = snapshots_api.list_snapshots( + placement_group=module.params["name"], + tenant_name=module.params["tenant"], + tenant_space_name=module.params["tenant_space"], + ) + for snap in snapshots.items: + delete_snapshot(fusion, snap, snapshots_api) + op = pg_api_instance.delete_placement_group( placement_group_name=module.params["name"], tenant_name=module.params["tenant"], @@ -229,6 +248,7 @@ def main(): argument_spec.update( dict( name=dict(type="str", required=True), + destroy_snapshots_on_delete=dict(type="bool"), display_name=dict(type="str"), tenant=dict(type="str", required=True), tenant_space=dict(type="str", required=True), @@ -257,19 +277,28 @@ def main(): state = module.params["state"] pgroup = get_pg(module, fusion) + id = None + if pgroup is not None: + id = pgroup.id + if state == "present" and not pgroup: module.fail_on_missing_params( ["region", "availability_zone", "storage_service"] ) - changed = create_pg(module, fusion) or changed + changed, id = create_pg(module, fusion) or changed if module.params["array"]: # changing placement requires additional update pgroup = get_pg(module, fusion) - changed = update_pg(module, fusion, pgroup) or changed + changedUpdate = update_pg(module, fusion, pgroup) + changed = changed | changedUpdate elif state == "present" and pgroup: changed = update_pg(module, fusion, pgroup) or changed elif state == "absent" and pgroup: changed = delete_pg(module, fusion) or changed + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py index abce9195c..216209d84 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py @@ -31,6 +31,11 @@ options: default: present choices: [ present, absent ] type: str + destroy_snapshots_on_delete: + description: + - "Before deleting protection policy, snapshots within the protection policy will be deleted." + - "If `false` then any snapshots will need to be deleted as a separate step before removing the protection policy." + type: bool display_name: description: - The human name of the protection policy. @@ -39,8 +44,10 @@ options: local_rpo: description: - Recovery Point Objective for snapshots. - - Value should be specified in minutes. - Minimum value is 10 minutes. + - Value can be provided as m(inutes), h(ours), + d(ays), w(eeks), or y(ears). + - If no unit is provided, minutes are assumed. type: str local_retention: description: @@ -95,6 +102,9 @@ from ansible_collections.purestorage.fusion.plugins.module_utils.startup import from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( await_operation, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( + delete_snapshot, +) def get_pp(module, fusion): @@ -114,11 +124,13 @@ def create_pp(module, fusion): pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) local_rpo = parse_minutes(module, module.params["local_rpo"]) local_retention = parse_minutes(module, module.params["local_retention"]) - if local_retention < 1: - module.fail_json(msg="Local Retention must be a minimum of 1 minutes") + if local_retention < 10: + module.fail_json(msg="Local Retention must be a minimum of 10 minutes") if local_rpo < 10: module.fail_json(msg="Local RPO must be a minimum of 10 minutes") + changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -136,9 +148,10 @@ def create_pp(module, fusion): ], ) ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_pp(module, fusion): @@ -146,6 +159,15 @@ def delete_pp(module, fusion): pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) changed = True if not module.check_mode: + if module.params["destroy_snapshots_on_delete"]: + protection_policy = get_pp(module, fusion) + snapshots_api = purefusion.SnapshotsApi(fusion) + snapshots = snapshots_api.query_snapshots( + protection_policy_id=protection_policy.id + ) + for snap in snapshots.items: + delete_snapshot(fusion, snap, snapshots_api) + op = pp_api_instance.delete_protection_policy( protection_policy_name=module.params["name"], ) @@ -160,6 +182,7 @@ def main(): argument_spec.update( dict( name=dict(type="str", required=True), + destroy_snapshots_on_delete=dict(type="bool"), display_name=dict(type="str"), local_rpo=dict(type="str"), local_retention=dict(type="str"), @@ -177,8 +200,6 @@ def main(): create_pp(module, fusion) elif policy and state == "absent": delete_pp(module, fusion) - else: - module.exit_json(changed=False) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py index 7cfc7d866..c2ae2d5cf 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py @@ -43,7 +43,7 @@ options: type: str api_client_key: description: - - The key of API client to assign the role to. + - The issuer ID of the API client to assign the role to. type: str scope: description: @@ -127,7 +127,7 @@ def get_principal(module, fusion): def user_to_principal(fusion, user_id): - """Given a human readable Fusion user, such as a Pure 1 App ID + """Given a human-readable Fusion user, such as a Pure 1 App ID return the associated principal """ id_api_instance = purefusion.IdentityManagerApi(fusion) @@ -139,7 +139,7 @@ def user_to_principal(fusion, user_id): def apiclient_to_principal(fusion, api_client_key): - """Given an API client key, such as "pure1:apikey:123xXxyYyzYzASDF" (also known as issuer_id), + """Given an API client issuer ID, such as "pure1:apikey:123xXxyYyzYzASDF", return the associated principal """ id_api_instance = purefusion.IdentityManagerApi(fusion) @@ -189,6 +189,7 @@ def create_ra(module, fusion): ra_api_instance = purefusion.RoleAssignmentsApi(fusion) changed = True + id = None if not module.check_mode: principal = get_principal(module, fusion) scope = get_scope(module.params) @@ -196,8 +197,10 @@ def create_ra(module, fusion): op = ra_api_instance.create_role_assignment( assignment, role_name=module.params["role"] ) - await_operation(fusion, op) - module.exit_json(changed=changed) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + + module.exit_json(changed=changed, id=id) def delete_ra(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py index fbcbff4b0..de40e7dc2 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py @@ -96,6 +96,7 @@ def create_region(module, fusion): reg_api_instance = purefusion.RegionsApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -106,9 +107,10 @@ def create_region(module, fusion): display_name=display_name, ) op = reg_api_instance.create_region(region) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_region(module, fusion): @@ -144,7 +146,7 @@ def update_region(module, fusion, region): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=region.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py index 2327b8d48..59fc0025e 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py @@ -160,6 +160,7 @@ def create_sc(module, fusion): module.fail_json(msg="Size limit is not within the required range") changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -175,9 +176,10 @@ def create_sc(module, fusion): op = sc_api_instance.create_storage_class( s_class, storage_service_name=module.params["storage_service"] ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_sc(module, fusion, s_class): @@ -201,7 +203,7 @@ def update_sc(module, fusion, s_class): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=s_class.id) def delete_sc(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py index 9eed4bea0..3a191a166 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py @@ -269,7 +269,7 @@ def get_se(module, fusion): def create_se(module, fusion): """Create Storage Endpoint""" se_api_instance = purefusion.StorageEndpointsApi(fusion) - + id = None if not module.check_mode: endpoint_type = None @@ -307,9 +307,10 @@ def create_se(module, fusion): region_name=module.params["region"], availability_zone_name=module.params["availability_zone"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=True) + module.exit_json(changed=True, id=id) def delete_se(module, fusion): @@ -351,7 +352,7 @@ def update_se(module, fusion, se): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=se.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py index 3fdbb07dd..4e6388249 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py @@ -106,6 +106,7 @@ def create_ss(module, fusion): ss_api_instance = purefusion.StorageServicesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -117,9 +118,10 @@ def create_ss(module, fusion): hardware_types=module.params["hardware_types"], ) op = ss_api_instance.create_storage_service(s_service) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_ss(module, fusion): @@ -151,6 +153,7 @@ def update_ss(module, fusion, ss): ) patches.append(patch) + id = None if not module.check_mode: for patch in patches: op = ss_api_instance.update_storage_service( @@ -161,7 +164,7 @@ def update_ss(module, fusion, ss): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=ss.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py index 96e890a6b..85224a6c5 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py @@ -87,6 +87,7 @@ def create_tenant(module, fusion): api_instance = purefusion.TenantsApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -97,9 +98,10 @@ def create_tenant(module, fusion): display_name=display_name, ) op = api_instance.create_tenant(tenant) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_tenant(module, fusion, tenant): @@ -122,7 +124,7 @@ def update_tenant(module, fusion, tenant): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=tenant.id) def delete_tenant(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py index 33fb0187a..ac60476bc 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py @@ -95,6 +95,7 @@ def create_ts(module, fusion): ts_api_instance = purefusion.TenantSpacesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -108,9 +109,10 @@ def create_ts(module, fusion): tspace, tenant_name=module.params["tenant"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_ts(module, fusion, ts): @@ -138,7 +140,7 @@ def update_ts(module, fusion, ts): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=ts.id) def delete_ts(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py index 5b19064f5..38dee8650 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py @@ -73,6 +73,21 @@ options: To clear, assign empty list: host_access_policies: []' type: list elements: str + source_volume: + description: + - The source volume name. It must live within the same tenant space. + Cannot be used together with `source_snapshot` or `source_volume_snapshot`. + type: str + source_snapshot: + description: + - The source snapshot name. It must live within the same tenant space. + Cannot be used together with `source_volume`. + type: str + source_volume_snapshot: + description: + - The source volume snapshot name. It must live within the same tenant space. + Cannot be used together with `source_volume`. + type: str rename: description: - New name for volume. @@ -86,6 +101,7 @@ EXAMPLES = r""" purestorage.fusion.fusion_volume: name: foo storage_class: fred + placement_group: pg size: 1T tenant: test tenant_space: space_1 @@ -93,6 +109,31 @@ EXAMPLES = r""" issuer_id: key_name private_key_file: "az-admin-private-key.pem" +- name: Create new volume based on a volume from the same tenant space + purestorage.fusion.fusion_volume: + name: foo + storage_class: fred + placement_group: pg + tenant: test + tenant_space: space_1 + state: present + source_volume: "original_volume_name" + issuer_id: key_name + private_key_file: "az-admin-private-key.pem" + +- name: Create new volume based on a volume snapshot from the same tenant space + purestorage.fusion.fusion_volume: + name: foo + storage_class: fred + placement_group: pg + tenant: test + tenant_space: space_1 + state: present + source_snapshot: "snap" + source_volume_snapshot: "vol_snap" + issuer_id: key_name + private_key_file: "az-admin-private-key.pem" + - name: Extend the size of an existing volume named foo purestorage.fusion.fusion_volume: name: foo @@ -116,24 +157,24 @@ EXAMPLES = r""" RETURN = r""" """ -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_number_with_metric_suffix, +from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( + await_operation, ) from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( setup_fusion, ) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, +from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( + parse_number_with_metric_suffix, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( + fusion_argument_spec, +) +from ansible.module_utils.basic import AnsibleModule + +try: + import fusion as purefusion +except ImportError: + pass def get_volume(module, fusion): @@ -166,28 +207,30 @@ def extract_current_haps(volume): def create_volume(module, fusion): """Create Volume""" - - size = parse_number_with_metric_suffix(module, module.params["size"]) - + id = None if not module.check_mode: display_name = module.params["display_name"] or module.params["name"] volume_api_instance = purefusion.VolumesApi(fusion) + source_link = get_source_link_from_parameters(module.params) volume = purefusion.VolumePost( - size=size, + size=None # when cloning a volume, size is not required + if source_link + else parse_number_with_metric_suffix(module, module.params["size"]), storage_class=module.params["storage_class"], placement_group=module.params["placement_group"], name=module.params["name"], display_name=display_name, protection_policy=module.params["protection_policy"], + source_link=source_link, ) op = volume_api_instance.create_volume( volume, tenant_name=module.params["tenant"], tenant_space_name=module.params["tenant_space"], ) - await_operation(fusion, op) - - return True + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + return True, id def update_host_access_policies(module, current, patches): @@ -273,6 +316,17 @@ def update_protection_policy(module, current, patches): patches.append(patch) +def update_source_link(module, fusion, current, patches): + source_link = get_source_link_from_parameters(module.params) + if source_link is not None and ( + current.source is None or current.source.self_link != source_link + ): + patch = purefusion.VolumePatch( + source_link=purefusion.NullableString(source_link) + ) + patches.append(patch) + + def apply_patches(module, fusion, patches): volume_api_instance = purefusion.VolumesApi(fusion) for patch in patches: @@ -313,6 +367,7 @@ def update_volume(module, fusion): update_storage_class(module, current, patches) update_placement_group(module, current, patches) update_host_access_policies(module, current, patches) + update_source_link(module, fusion, current, patches) elif module.params["state"] == "absent" and not current.destroyed: update_size(module, current, patches) update_protection_policy(module, current, patches) @@ -320,6 +375,7 @@ def update_volume(module, fusion): update_storage_class(module, current, patches) update_placement_group(module, current, patches) update_host_access_policies(module, current, patches) + update_source_link(module, fusion, current, patches) update_destroyed(module, current, patches) if not module.check_mode: @@ -355,16 +411,46 @@ def eradicate_volume(module, fusion): return True +def get_source_link_from_parameters(params): + tenant = params["tenant"] + tenant_space = params["tenant_space"] + volume = params["source_volume"] + snapshot = params["source_snapshot"] + volume_snapshot = params["source_volume_snapshot"] + if ( + tenant is None or tenant_space is None + ): # should not happen as those parameters are always required by the ansible module + return None + if volume is not None: + return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/volumes/{volume}" + if snapshot is not None and volume_snapshot is not None: + return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/snapshots/{snapshot}/volume-snapshots/{volume_snapshot}" + return None + + def validate_arguments(module, volume): """Validates most argument conditions and possible unacceptable argument combinations""" state = module.params["state"] if state == "present" and not volume: - module.fail_on_missing_params(["placement_group", "storage_class", "size"]) + module.fail_on_missing_params(["placement_group", "storage_class"]) + + if ( + module.params["size"] is None + and module.params["source_volume"] is None + and module.params["source_snapshot"] is None + ): + module.fail_json( + msg="Either `size`, `source_volume` or `source_snapshot` parameter is required when creating a volume." + ) if module.params["state"] == "absent" and ( module.params["host_access_policies"] - or (volume and volume.host_access_policies) + or ( + module.params["host_access_policies"] is None + and volume + and volume.host_access_policies + ) ): module.fail_json( msg=( @@ -378,7 +464,7 @@ def validate_arguments(module, volume): msg="'eradicate: true' cannot be used together with 'state: present'" ) - if module.params["size"]: + if module.params["size"] is not None: size = parse_number_with_metric_suffix(module, module.params["size"]) if size < 1048576 or size > 4503599627370496: # 1MB to 4PB module.fail_json( @@ -412,6 +498,9 @@ def main(): eradicate=dict(type="bool", default=False), state=dict(type="str", default="present", choices=["absent", "present"]), size=dict(type="str"), + source_volume=dict(type="str"), + source_snapshot=dict(type="str"), + source_volume_snapshot=dict(type="str"), ) ) @@ -419,9 +508,22 @@ def main(): "placement_group": "storage_class", } + mutually_exclusive = [ + # a new volume cannot be based on a volume and a snapshot at the same time + # also, when cloning a volume, size of original volume is used + ("source_volume", "source_snapshot", "size"), + ] + + required_together = [ + # when creating a volume from snapshot, we need to know both snapshot name and snapshot volume name + ("source_snapshot", "source_volume_snapshot"), + ] + module = AnsibleModule( argument_spec, required_by=required_by, + mutually_exclusive=mutually_exclusive, + required_together=required_together, supports_check_mode=True, ) fusion = setup_fusion(module) @@ -436,12 +538,19 @@ def main(): module.exit_json(changed=False) changed = False + id = None + if volume is not None: + id = volume.id if state == "present" and not volume: - changed = changed | create_volume(module, fusion) + changed, id = create_volume(module, fusion) # volume might exist even if soft-deleted, so we still have to update it changed = changed | update_volume(module, fusion) if module.params["eradicate"]: changed = changed | eradicate_volume(module, fusion) + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) diff --git a/ansible_collections/purestorage/fusion/test/config.yaml b/ansible_collections/purestorage/fusion/test/config.yaml new file mode 100644 index 000000000..9e402bda7 --- /dev/null +++ b/ansible_collections/purestorage/fusion/test/config.yaml @@ -0,0 +1,2 @@ +modules: + python_requires: ">=3.6" diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_api_client.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_api_client.py index 77f753656..295c62bd6 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_api_client.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_api_client.py @@ -147,7 +147,19 @@ def test_api_client_create(m_im_api, current_clients): api_obj = MagicMock() api_obj.list_api_clients = MagicMock(return_value=current_clients) api_obj.get_api_client = MagicMock(side_effect=purefusion.rest.ApiException) - api_obj.create_api_client = MagicMock() + api_obj.create_api_client = MagicMock( + return_value=FakeApiClient( + "321321", + "self_link_value", + "client_test", + "client_test", + "apikey:name:test", + "321321", + 321321, + 321321, + "321321", + ) + ) api_obj.delete_api_client = MagicMock() m_im_api.return_value = api_obj @@ -156,6 +168,7 @@ def test_api_client_create(m_im_api, current_clients): fusion_api_client.main() assert exc.value.changed is True + assert exc.value.id == "321321" # check api was called correctly api_obj.list_api_clients.assert_called_once_with() diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_array.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_array.py index 0343bb1dc..6af1e1136 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_array.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_array.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -46,6 +47,7 @@ def module_args(): "region": "region1", "availability_zone": "az1", "appliance_id": "23984573498573", + "apartment_id": "76586785687", "host_name": "array_1", "hardware_type": "flash-array-x", "maintenance_mode": False, @@ -65,7 +67,7 @@ def current_array(module_args): "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": module_args["maintenance_mode"], @@ -332,7 +334,7 @@ def test_array_create(m_array_api, m_op_api, hw_type, main_m, unav_m, module_arg "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": not module_args[ @@ -364,6 +366,7 @@ def test_array_create(m_array_api, m_op_api, hw_type, main_m, unav_m, module_arg fusion_array.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_array.assert_called_with( @@ -378,6 +381,7 @@ def test_array_create(m_array_api, m_op_api, hw_type, main_m, unav_m, module_arg host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -429,7 +433,7 @@ def test_array_create_without_display_name(m_array_api, m_op_api, module_args): "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": not module_args["maintenance_mode"], @@ -457,6 +461,7 @@ def test_array_create_without_display_name(m_array_api, m_op_api, module_args): fusion_array.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_array.assert_called_with( @@ -471,6 +476,7 @@ def test_array_create_without_display_name(m_array_api, m_op_api, module_args): host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -554,6 +560,7 @@ def test_array_create_exception( host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -583,7 +590,7 @@ def test_array_create_second_exception( "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": not module_args["maintenance_mode"], @@ -623,6 +630,7 @@ def test_array_create_second_exception( host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -667,6 +675,7 @@ def test_array_create_op_fails(m_array_api, m_op_api, module_args): host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -687,7 +696,7 @@ def test_array_create_second_op_fails(m_array_api, m_op_api, module_args): "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": not module_args["maintenance_mode"], @@ -729,6 +738,7 @@ def test_array_create_second_op_fails(m_array_api, m_op_api, module_args): host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -787,6 +797,7 @@ def test_array_create_op_exception( host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -816,7 +827,7 @@ def test_array_create_second_op_exception( "region": module_args["region"], "availability_zone": module_args["availability_zone"], "appliance_id": module_args["appliance_id"], - "apartment_id": "76586785687", + "apartment_id": module_args["apartment_id"], "host_name": module_args["host_name"], "hardware_type": module_args["hardware_type"], "maintenance_mode": not module_args["maintenance_mode"], @@ -858,6 +869,7 @@ def test_array_create_second_op_exception( host_name=module_args["host_name"], name=module_args["name"], appliance_id=module_args["appliance_id"], + apartment_id=module_args["apartment_id"], ), availability_zone_name=module_args["availability_zone"], region_name=module_args["region"], @@ -899,6 +911,7 @@ def test_array_update(m_array_api, m_op_api, module_args, current_array): fusion_array.main() assert exc.value.changed + assert exc.value.id == current_array["id"] # check api was called correctly api_obj.get_array.assert_called_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_az.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_az.py index c49f958a2..d19e41827 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_az.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_az.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -139,6 +140,7 @@ def test_az_create(m_az_api, m_op_api): fusion_az.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID api_obj.get_region.get_availability_zone( availability_zone_name=module_args["name"], @@ -186,6 +188,7 @@ def test_az_create_without_display_name(m_az_api, m_op_api): fusion_az.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID api_obj.get_region.get_availability_zone( availability_zone_name=module_args["name"], diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_hap.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_hap.py index 6491c71da..258ca2034 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_hap.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_hap.py @@ -23,6 +23,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -295,6 +296,7 @@ def test_hap_create(m_hap_api, m_op_api, module_args, current_hap_list): fusion_hap.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.list_host_access_policies.assert_called_once_with() @@ -341,6 +343,7 @@ def test_hap_create_without_display_name( fusion_hap.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.list_host_access_policies.assert_called_once_with() diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_info.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_info.py index 784b550cd..c542cddc0 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_info.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_info.py @@ -847,6 +847,7 @@ RESP_VS = purefusion.VolumeSnapshotList( @patch.dict(os.environ, {"TZ": "UTC"}) +@patch.dict(os.environ, {"LC_TIME": "en_US.utf8"}) @patch("fusion.DefaultApi") @patch("fusion.IdentityManagerApi") @patch("fusion.ProtectionPoliciesApi") diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_nig.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_nig.py index 3a7b7ca5c..e8a2eb0ac 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_nig.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_nig.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -236,6 +237,7 @@ def test_nig_create(m_nig_api, m_op_api): fusion_nig.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_network_interface_group.assert_called_once_with( @@ -299,6 +301,7 @@ def test_nig_create_without_display_name(m_nig_api, m_op_api): fusion_nig.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_network_interface_group.assert_called_once_with( @@ -362,6 +365,7 @@ def test_nig_create_without_gateway(m_nig_api, m_op_api): fusion_nig.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_network_interface_group.assert_called_once_with( @@ -631,6 +635,7 @@ def test_nig_update(m_nig_api, m_op_api): fusion_nig.main() assert exc.value.changed is True + assert exc.value.id == current_nig.id # check api was called correctly api_obj.get_network_interface_group.assert_called_once_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pg.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pg.py index 2f0601e12..2a9419a8e 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pg.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pg.py @@ -20,6 +20,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( AnsibleExitJson, AnsibleFailJson, OperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -221,6 +222,7 @@ def test_pg_create_ok(pg_api_init, op_api_init, module_args_present): with pytest.raises(AnsibleExitJson) as excinfo: fusion_pg.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID pg_mock.get_placement_group.assert_called_with( tenant_name="tenant1", @@ -265,6 +267,7 @@ def test_pg_create_without_display_name_ok( with pytest.raises(AnsibleExitJson) as excinfo: fusion_pg.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID pg_mock.get_placement_group.assert_called_with( tenant_name="tenant1", @@ -450,6 +453,7 @@ def test_pg_create_triggers_update_ok(pg_api_init, op_api_init): with pytest.raises(AnsibleExitJson) as excinfo: fusion_pg.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID pg_mock.get_placement_group.assert_has_calls( [ @@ -946,6 +950,7 @@ def test_pg_update_ok(pg_api_init, op_api_init, test_case): with pytest.raises(AnsibleExitJson) as excinfo: fusion_pg.main() assert excinfo.value.changed + assert excinfo.value.id == test_case["current_state"].id pg_mock.get_placement_group.assert_called_with( tenant_name="tenant1", diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pp.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pp.py index 519caea40..359d4ca7e 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pp.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_pp.py @@ -20,6 +20,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( AnsibleExitJson, AnsibleFailJson, OperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -39,7 +40,7 @@ basic.AnsibleModule.fail_json = fail_json def module_args_present(): return { "name": "protection_policy1", - "local_rpo": 43, + "local_rpo": "1H43M", "local_retention": "2H", "state": "present", "issuer_id": "ABCD1234", @@ -181,6 +182,7 @@ def test_pp_create_ok(pp_api_init, op_api_init, module_args_present): with pytest.raises(AnsibleExitJson) as excinfo: fusion_pp.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID pp_mock.get_protection_policy.assert_called_with( protection_policy_name="protection_policy1" @@ -190,7 +192,7 @@ def test_pp_create_ok(pp_api_init, op_api_init, module_args_present): name="protection_policy1", display_name="some_display_name", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -220,6 +222,7 @@ def test_pp_create_without_display_name_ok( with pytest.raises(AnsibleExitJson) as excinfo: fusion_pp.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID pp_mock.get_protection_policy.assert_called_with( protection_policy_name="protection_policy1" @@ -229,7 +232,7 @@ def test_pp_create_without_display_name_ok( name="protection_policy1", display_name="protection_policy1", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -274,7 +277,7 @@ def test_pp_create_exception( name="protection_policy1", display_name="protection_policy1", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -310,7 +313,7 @@ def test_pp_create_op_fails(pp_api_init, op_api_init, module_args_present): name="protection_policy1", display_name="protection_policy1", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -333,7 +336,7 @@ def test_pp_delete_ok(pp_api_init, op_api_init, module_args_absent): display_name="protection_policy1_display_name", self_link="test_self_link", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -385,7 +388,7 @@ def test_pp_delete_exception( display_name="protection_policy1_display_name", self_link="test_self_link", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -425,7 +428,7 @@ def test_pp_delete_op_fails(pp_api_init, op_api_init, module_args_absent): display_name="protection_policy1_display_name", self_link="test_self_link", objectives=[ - purefusion.RPO(type="RPO", rpo="PT43M"), + purefusion.RPO(type="RPO", rpo="PT103M"), purefusion.Retention(type="Retention", after="PT120M"), ], ) @@ -459,7 +462,7 @@ def test_pp_present_not_changed(pp_api_init, op_api_init): module_args = { "name": "protection_policy1", "display_name": "some_display_name", - "local_rpo": 43, + "local_rpo": "43M", "local_retention": "2H", "state": "present", "issuer_id": "ABCD1234", diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ra.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ra.py index 6456fa7d7..d8cac74a5 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ra.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ra.py @@ -20,6 +20,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( AnsibleExitJson, AnsibleFailJson, OperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -330,6 +331,7 @@ def test_ra_create_ok(ra_api_init, im_api_init, op_api_init, args_and_scope): with pytest.raises(AnsibleExitJson) as excinfo: fusion_ra.main() assert excinfo.value.changed + assert excinfo.value.id == FAKE_RESOURCE_ID ra_mock.list_role_assignments.assert_called_with( role_name=module_args["role"], principal="principal1" diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_region.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_region.py index 6b13adecf..42d14d56e 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_region.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_region.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -126,6 +127,7 @@ def test_region_create(m_region_api, m_op_api): fusion_region.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_region.assert_called_once_with(region_name=module_args["name"]) @@ -168,6 +170,7 @@ def test_region_create_without_display_name(m_region_api, m_op_api): fusion_region.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_region.assert_called_once_with(region_name=module_args["name"]) @@ -354,6 +357,7 @@ def test_region_update(m_region_api, m_op_api): fusion_region.main() assert exc.value.changed + assert exc.value.id == current_region["id"] # check api was called correctly api_obj.get_region.assert_called_once_with(region_name=module_args["name"]) diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_sc.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_sc.py index 1a2db191c..4d44e7fcb 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_sc.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_sc.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -167,6 +168,7 @@ def test_sc_create( fusion_sc.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_class.assert_called_once_with( @@ -224,6 +226,7 @@ def test_sc_create_without_display_name(m_sc_api, m_op_api): fusion_sc.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_class.assert_called_once_with( @@ -608,6 +611,7 @@ def test_sc_update(m_sc_api, m_op_api): fusion_sc.main() assert exc.value.changed + assert exc.value.id == current_sc["id"] # check api was called correctly api_obj.get_storage_class.assert_called_once_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_se.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_se.py index a071190db..9d9559c12 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_se.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_se.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -285,6 +286,7 @@ def test_se_create_iscsi(m_se_api, m_op_api, module_args): fusion_se.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_endpoint.assert_called_once_with( @@ -341,6 +343,7 @@ def test_se_create_cbs_azure_iscsi(m_se_api, m_op_api, module_args): fusion_se.main() assert exc.value.changed is True + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_endpoint.assert_called_once_with( @@ -395,6 +398,7 @@ def test_se_create_without_display_name(m_se_api, m_op_api, module_args): fusion_se.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_endpoint.assert_called_once_with( @@ -610,6 +614,7 @@ def test_se_update(m_se_api, m_op_api, module_args, current_se): fusion_se.main() assert exc.value.changed + assert exc.value.id == current_se["id"] # check api was called correctly api_obj.get_storage_endpoint.assert_called_once_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ss.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ss.py index d784b1a52..f1514b8e6 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ss.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ss.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -139,6 +140,7 @@ def test_ss_create(m_ss_api, m_op_api): fusion_ss.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_service.assert_called_once_with( @@ -186,6 +188,7 @@ def test_ss_create_without_display_name(m_ss_api, m_op_api): fusion_ss.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_storage_service.assert_called_once_with( @@ -434,6 +437,7 @@ def test_ss_update(m_ss_api, m_op_api): fusion_ss.main() assert exc.value.changed + assert exc.value.id == current_ss["id"] # check api was called correctly api_obj.get_storage_service.assert_called_once_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_tenant.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_tenant.py index bb0521b01..11cd71171 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_tenant.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_tenant.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -126,6 +127,7 @@ def test_tenant_create(m_tenant_api, m_op_api): fusion_tenant.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_tenant.assert_called_once_with(tenant_name=module_args["name"]) @@ -169,6 +171,7 @@ def test_tenant_create_without_display_name(m_tenant_api, m_op_api): fusion_tenant.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_tenant.assert_called_once_with(tenant_name=module_args["name"]) @@ -359,6 +362,7 @@ def test_tenant_update(m_tenant_api, m_op_api): fusion_tenant.main() assert exc.value.changed + assert exc.value.id == current_tenant["id"] # check api was called correctly api_obj.get_tenant.assert_called_once_with(tenant_name=module_args["name"]) diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ts.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ts.py index 0d9cbb25a..0e1260858 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ts.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_ts.py @@ -22,6 +22,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( FailedOperationMock, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -138,6 +139,7 @@ def test_ts_create(m_ts_api, m_op_api): fusion_ts.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_tenant_space.assert_called_once_with( @@ -186,6 +188,7 @@ def test_ts_create_without_display_name(m_ts_api, m_op_api): fusion_ts.main() assert exc.value.changed + assert exc.value.id == FAKE_RESOURCE_ID # check api was called correctly api_obj.get_tenant_space.assert_called_once_with( @@ -399,6 +402,7 @@ def test_ts_update(m_ts_api, m_op_api): fusion_ts.main() assert exc.value.changed + assert exc.value.id == current_ts["id"] # check api was called correctly api_obj.get_tenant_space.assert_called_once_with( diff --git a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_volume.py b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_volume.py index 592bda32e..43f69666e 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/test_fusion_volume.py +++ b/ansible_collections/purestorage/fusion/tests/functional/test_fusion_volume.py @@ -7,7 +7,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, call, patch import fusion as purefusion import pytest @@ -23,6 +23,7 @@ from ansible_collections.purestorage.fusion.tests.functional.utils import ( AnsibleFailJson, OperationMock, SuccessfulOperationMock, + FAKE_RESOURCE_ID, exit_json, fail_json, set_module_args, @@ -126,7 +127,7 @@ def destroyed_volume(volume): ), ( "size", - "missing required arguments: size", + "Either `size`, `source_volume` or `source_snapshot` parameter is required when creating a volume.", ), ], ) @@ -164,6 +165,18 @@ def test_module_fails_on_missing_parameters( {"size": "1K"}, "Size is not within the required range", ), + ( + {"source_volume": "vol_name"}, + "parameters are mutually exclusive: source_volume|source_snapshot|size", + ), + ( + {"source_snapshot": "snap_name"}, + "parameters are mutually exclusive: source_volume|source_snapshot|size", + ), + ( + {"source_volume_snapshot": "vol_snap_name"}, + "parameters are required together: source_snapshot, source_volume_snapshot", + ), ], ) def test_module_fails_on_incorrect_parameters( @@ -216,6 +229,8 @@ def test_volume_create_successfully(mock_volumes_api, mock_operations_api, modul with pytest.raises(AnsibleExitJson) as exception: fusion_volume.main() assert exception.value.changed is True + assert exception.value.id == FAKE_RESOURCE_ID + volumes_api.get_volume.assert_called_with( volume_name=module_args["name"], tenant_name=module_args["tenant"], @@ -238,6 +253,90 @@ def test_volume_create_successfully(mock_volumes_api, mock_operations_api, modul @patch("fusion.OperationsApi") @patch("fusion.VolumesApi") +def test_volume_create_from_volume_successfully( + mock_volumes_api, mock_operations_api, module_args +): + del module_args["size"] + module_args["source_volume"] = "source_volume_name" + + operations_api = purefusion.OperationsApi() + volumes_api = purefusion.VolumesApi() + volumes_api.get_volume = MagicMock(side_effect=purefusion.rest.ApiException) + volumes_api.create_volume = MagicMock(return_value=OperationMock(1)) + operations_api.get_operation = MagicMock(return_value=SuccessfulOperationMock) + mock_volumes_api.return_value = volumes_api + mock_operations_api.return_value = operations_api + set_module_args(module_args) + # run module + with pytest.raises(AnsibleExitJson) as exception: + fusion_volume.main() + assert exception.value.changed is True + assert exception.value.id == FAKE_RESOURCE_ID + volumes_api.get_volume.assert_called_with( + volume_name=module_args["name"], + tenant_name=module_args["tenant"], + tenant_space_name=module_args["tenant_space"], + ) + volumes_api.create_volume.assert_called_once_with( + purefusion.VolumePost( + source_link=f"/tenants/{module_args['tenant']}/tenant-spaces/{module_args['tenant_space']}/volumes/{module_args['source_volume']}", + storage_class=module_args["storage_class"], + placement_group=module_args["placement_group"], + name=module_args["name"], + display_name=module_args["display_name"], + protection_policy=module_args["protection_policy"], + ), + tenant_name=module_args["tenant"], + tenant_space_name=module_args["tenant_space"], + ) + operations_api.get_operation.assert_called_once_with(1) + + +@patch("fusion.OperationsApi") +@patch("fusion.VolumesApi") +def test_volume_create_from_volume_snapshot_successfully( + mock_volumes_api, mock_operations_api, module_args +): + del module_args["size"] + module_args["source_snapshot"] = "source_snapshot_name" + module_args["source_volume_snapshot"] = "source_volume_snapshot_name" + + operations_api = purefusion.OperationsApi() + volumes_api = purefusion.VolumesApi() + volumes_api.get_volume = MagicMock(side_effect=purefusion.rest.ApiException) + volumes_api.create_volume = MagicMock(return_value=OperationMock(1)) + operations_api.get_operation = MagicMock(return_value=SuccessfulOperationMock) + mock_volumes_api.return_value = volumes_api + mock_operations_api.return_value = operations_api + set_module_args(module_args) + # run module + with pytest.raises(AnsibleExitJson) as exception: + fusion_volume.main() + assert exception.value.changed is True + assert exception.value.id == FAKE_RESOURCE_ID + volumes_api.get_volume.assert_called_with( + volume_name=module_args["name"], + tenant_name=module_args["tenant"], + tenant_space_name=module_args["tenant_space"], + ) + volumes_api.create_volume.assert_called_once_with( + purefusion.VolumePost( + source_link=f"/tenants/{module_args['tenant']}/tenant-spaces/{module_args['tenant_space']}/snapshots/" + f"{module_args['source_snapshot']}/volume-snapshots/{module_args['source_volume_snapshot']}", + storage_class=module_args["storage_class"], + placement_group=module_args["placement_group"], + name=module_args["name"], + display_name=module_args["display_name"], + protection_policy=module_args["protection_policy"], + ), + tenant_name=module_args["tenant"], + tenant_space_name=module_args["tenant_space"], + ) + operations_api.get_operation.assert_called_once_with(1) + + +@patch("fusion.OperationsApi") +@patch("fusion.VolumesApi") def test_volume_create_without_display_name_successfully( mock_volumes_api, mock_operations_api, module_args ): @@ -254,6 +353,7 @@ def test_volume_create_without_display_name_successfully( with pytest.raises(AnsibleExitJson) as exception: fusion_volume.main() assert exception.value.changed is True + assert exception.value.id == FAKE_RESOURCE_ID volumes_api.get_volume.assert_called_with( volume_name=module_args["name"], tenant_name=module_args["tenant"], @@ -399,6 +499,7 @@ def test_volume_update_with_state_present_executed_correctly( with pytest.raises(AnsibleExitJson) as exception: fusion_volume.main() assert exception.value.changed is True + assert exception.value.id == volume["id"] volumes_api.get_volume.assert_called_with( volume_name=module_args["name"], tenant_name=module_args["tenant"], @@ -447,6 +548,7 @@ def test_volume_update_with_state_absent_executed_correctly( with pytest.raises(AnsibleExitJson) as exception: fusion_volume.main() assert exception.value.changed is True + assert exception.value.id == volume["id"] volumes_api.get_volume.assert_called_with( volume_name=module_args["name"], tenant_name=module_args["tenant"], @@ -713,3 +815,47 @@ def test_volume_delete_operation_throws_exception( tenant_space_name=absent_module_args["tenant_space"], ) operations_api.get_operation.assert_called_once_with(2) + + +@patch("fusion.OperationsApi") +@patch("fusion.VolumesApi") +def test_module_updates_on_empty_array_of_haps( + mock_volumes_api, mock_operations_api, module_args, volume +): + volumes_api = purefusion.VolumesApi() + operations_api = purefusion.OperationsApi() + volumes_api.get_volume = MagicMock(return_value=purefusion.Volume(**volume)) + volumes_api.update_volume = MagicMock(return_value=OperationMock(1)) + operations_api.get_operation = MagicMock(return_value=SuccessfulOperationMock) + mock_operations_api.return_value = operations_api + mock_volumes_api.return_value = volumes_api + module_args.update({"state": "absent", "host_access_policies": []}) + set_module_args(module_args) + # run module + with pytest.raises(AnsibleExitJson) as exception: + fusion_volume.main() + assert exception.value.changed is True + assert exception.value.id == volume["id"] + volumes_api.get_volume.assert_called_with( + volume_name=module_args["name"], + tenant_name=module_args["tenant"], + tenant_space_name=module_args["tenant_space"], + ) + volumes_api.update_volume.assert_has_calls( + [ + call( + purefusion.VolumePatch( + host_access_policies=purefusion.NullableString(",".join([])) + ), + volume_name=volume["name"], + tenant_name=volume["tenant"], + tenant_space_name=volume["tenant_space"], + ), + call( + purefusion.VolumePatch(destroyed=purefusion.NullableBoolean(True)), + volume_name=volume["name"], + tenant_name=volume["tenant"], + tenant_space_name=volume["tenant_space"], + ), + ] + ) diff --git a/ansible_collections/purestorage/fusion/tests/functional/utils.py b/ansible_collections/purestorage/fusion/tests/functional/utils.py index 24d6f0328..53e501bc0 100644 --- a/ansible_collections/purestorage/fusion/tests/functional/utils.py +++ b/ansible_collections/purestorage/fusion/tests/functional/utils.py @@ -7,6 +7,11 @@ from dataclasses import dataclass from ansible.module_utils import basic from ansible.module_utils.common.text.converters import to_bytes +from ansible_collections.purestorage.fusion.tests.helpers import ( + OperationResultsDict, +) + +FAKE_RESOURCE_ID = "fake-id-12345" @dataclass @@ -20,6 +25,9 @@ class OperationMock: self.status = "Pending" elif success: self.status = "Succeeded" + self.result = OperationResultsDict( + {"resource": OperationResultsDict({"id": FAKE_RESOURCE_ID})} + ) else: self.status = "Failed" self.id = id @@ -30,6 +38,9 @@ class SuccessfulOperationMock: Mock object for successful operation. This object is returned by mocked Operation API if the operation was successful. """ + result = OperationResultsDict( + {"resource": OperationResultsDict({"id": FAKE_RESOURCE_ID})} + ) status = "Succeeded" @@ -65,6 +76,10 @@ class AnsibleExitJson(Exception): return self.kwargs["changed"] @property + def id(self): + return self.kwargs["id"] + + @property def fusion_info(self): return self.kwargs["fusion_info"] if "fusion_info" in self.kwargs else None diff --git a/ansible_collections/purestorage/fusion/tests/helpers.py b/ansible_collections/purestorage/fusion/tests/helpers.py index 40d98cf0e..76d51b6f7 100644 --- a/ansible_collections/purestorage/fusion/tests/helpers.py +++ b/ansible_collections/purestorage/fusion/tests/helpers.py @@ -27,3 +27,11 @@ class ApiExceptionsMockGenerator: def create_not_found(): status = HTTPStatus.NOT_FOUND return purefusion.rest.ApiException(status=status, reason=status.phrase) + + +class OperationResultsDict(dict): + """dot.notation access to dictionary attributes""" + + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ diff --git a/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py b/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py index 99487ddfa..a3a70c67d 100644 --- a/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py +++ b/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py @@ -8,6 +8,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type from enum import Enum +from ansible_collections.purestorage.fusion.tests.helpers import ( + OperationResultsDict, +) class OperationStatus(str, Enum): @@ -18,7 +21,16 @@ class OperationStatus(str, Enum): class OperationMock: - def __init__(self, id, status, retry_in=1): + def __init__( + self, + id, + status, + result=OperationResultsDict( + {"resource": OperationResultsDict({"id": "fake-id"})} + ), + retry_in=1, + ): self.id = id self.status = status self.retry_in = retry_in + self.result = result diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py index 7e2a1cc78..230d0ff01 100644 --- a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py @@ -7,13 +7,12 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import pytest from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_number_with_metric_suffix, parse_minutes, + parse_number_with_metric_suffix, ) -import pytest - class MockException(Exception): pass @@ -83,17 +82,37 @@ def test_parsing_invalid_number(): def test_parsing_valid_time_period(): module = MockModule() + assert parse_minutes(module, "0") == 0 + assert parse_minutes(module, "00") == 0 + assert parse_minutes(module, "00M") == 0 assert parse_minutes(module, "10") == 10 - assert parse_minutes(module, "2h") == 120 - assert parse_minutes(module, "2H") == 120 + assert parse_minutes(module, "015") == 15 + assert parse_minutes(module, "0023") == 23 + assert parse_minutes(module, "0H10M") == 10 + assert parse_minutes(module, "2h") == 2 * 60 + assert parse_minutes(module, "2H") == 2 * 60 + assert parse_minutes(module, "02h") == 2 * 60 + assert parse_minutes(module, "02H") == 2 * 60 + assert parse_minutes(module, "002h") == 2 * 60 + assert parse_minutes(module, "002H") == 2 * 60 + assert parse_minutes(module, "0D10H10M") == 10 * 60 + 10 assert parse_minutes(module, "14D") == 14 * 24 * 60 + assert parse_minutes(module, "014D") == 14 * 24 * 60 + assert parse_minutes(module, "0000014D") == 14 * 24 * 60 assert parse_minutes(module, "1W") == 7 * 24 * 60 + assert parse_minutes(module, "01W") == 7 * 24 * 60 + assert parse_minutes(module, "01Y0H10M") == 365 * 24 * 60 + 10 assert parse_minutes(module, "12Y") == 12 * 365 * 24 * 60 + assert parse_minutes(module, "012Y") == 12 * 365 * 24 * 60 assert ( parse_minutes(module, "10Y20W30D40H50M") == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 + 50 ) assert ( + parse_minutes(module, "010Y20W30D40H50M") + == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 + 50 + ) + assert ( parse_minutes(module, "10Y20W30D40H") == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 ) @@ -110,6 +129,10 @@ def test_parsing_valid_time_period(): assert parse_minutes(module, "40H50M") == 40 * 60 + 50 assert parse_minutes(module, "30D50M") == 30 * 24 * 60 + 50 assert parse_minutes(module, "20W40H") == 20 * 7 * 24 * 60 + 40 * 60 + assert ( + parse_minutes(module, "01W000010D10H10M") + == 7 * 24 * 60 + 10 * 24 * 60 + 10 * 60 + 10 + ) def test_parsing_invalid_time_period(): @@ -123,16 +146,8 @@ def test_parsing_invalid_time_period(): with pytest.raises(MockException): assert parse_minutes(module, "1V") with pytest.raises(MockException): - assert parse_minutes(module, "0M") - with pytest.raises(MockException): - assert parse_minutes(module, "0H10M") - with pytest.raises(MockException): - assert parse_minutes(module, "0H10M") + assert parse_minutes(module, "1v") with pytest.raises(MockException): - assert parse_minutes(module, "0D10H10M") + assert parse_minutes(module, "10M2H") with pytest.raises(MockException): - assert parse_minutes(module, "01W10D10H10M") - with pytest.raises(MockException): - assert parse_minutes(module, "01Y0H10M") - with pytest.raises(MockException): - assert parse_minutes(module, "1V") + assert parse_minutes(module, "0H10M01Y") diff --git a/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py b/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py index a384506d8..ee300638e 100644 --- a/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py +++ b/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py @@ -81,7 +81,9 @@ class TestCreateAZ: azone, region_name=module_params["region"] ) await_operation_mock.assert_called_once_with(fusion_mock, op) - moduleMock.exit_json.assert_called_once_with(changed=True) + moduleMock.exit_json.assert_called_once_with( + changed=True, id=op.result.resource.id + ) @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") @patch(f"{current_module}.fusion_az.await_operation") @@ -113,7 +115,7 @@ class TestCreateAZ: # Assertions mock_az_api_obj.create_availability_zone.assert_not_called() await_operation_mock.assert_not_called() - moduleMock.exit_json.assert_called_once_with(changed=True) + moduleMock.exit_json.assert_called_once_with(changed=True, id=None) @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") @patch(f"{current_module}.fusion_az.await_operation") @@ -151,7 +153,9 @@ class TestCreateAZ: azone, region_name=module_params["region"] ) await_operation_mock.assert_called_once_with(fusion_mock, op) - moduleMock.exit_json.assert_called_once_with(changed=True) + moduleMock.exit_json.assert_called_once_with( + changed=True, id=op.result.resource.id + ) @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") @patch(f"{current_module}.fusion_az.await_operation") |