diff --git a/.github/workflows/check_pr_plugin_vsphere.yml b/.github/workflows/check_pr_plugin_vsphere.yml deleted file mode 100644 index cdec361e99..0000000000 --- a/.github/workflows/check_pr_plugin_vsphere.yml +++ /dev/null @@ -1,75 +0,0 @@ -# Note: this workflow is automatically generated via the `create_pr` script in the same folder. -# Please do not change the file, but the script! - -name: Check PR (Plugin vsphere) -on: - push: - tags: - - "*.*.*" - branches: - - main - pull_request: - paths: - - 'fixlib/**' - - 'plugins/vsphere/**' - - '.github/**' - - 'requirements-all.txt' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true - -jobs: - vsphere: - name: "vsphere" - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - architecture: 'x64' - - - name: Restore dependency cache - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{runner.os}}-pip-${{hashFiles('./plugins/vsphere/pyproject.toml')}} - restore-keys: | - ${{runner.os}}-pip- - - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install --upgrade --editable fixlib/ - pip install tox wheel flake8 build - - - name: Run tests - working-directory: ./plugins/vsphere - run: tox - - - name: Archive code coverage results - uses: actions/upload-artifact@v4 - with: - name: plugin-vsphere-code-coverage-report - path: ./plugins/vsphere/htmlcov/ - - - name: Build a binary wheel and a source tarball - working-directory: ./plugins/vsphere - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - - - name: Publish distribution to PyPI - if: github.ref_type == 'tag' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_FIXINVENTORY_PLUGIN_VSPHERE }} - packages_dir: ./plugins/vsphere/dist/ diff --git a/.github/workflows/model_check.yml b/.github/workflows/model_check.yml index 7673d7550c..3fbcdbece9 100644 --- a/.github/workflows/model_check.yml +++ b/.github/workflows/model_check.yml @@ -17,7 +17,6 @@ on: - 'plugins/onelogin/**' - 'plugins/onprem/**' - 'plugins/slack/**' - - 'plugins/vsphere/**' - '.github/**' - 'requirements-all.txt' workflow_dispatch: @@ -53,7 +52,8 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-all.txt - pip install fixlib/ plugins/aws/ plugins/azure/ plugins/digitalocean/ plugins/dockerhub/ plugins/example_collector/ plugins/gcp/ plugins/github/ plugins/k8s/ plugins/onelogin/ plugins/onprem/ plugins/posthog/ plugins/random/ plugins/scarf/ plugins/slack/ plugins/vsphere/ + pip install fixlib/ plugins/aws/ plugins/azure/ plugins/digitalocean/ plugins/dockerhub/ plugins/example_collector/ plugins/gcp/ plugins/github/ plugins/k8s/ plugins/onelogin/ plugins/onprem/ plugins/posthog/ plugins/random/ plugins/scarf/ plugins/slack/ + pip install fixinventory-plugin-vsphere - name: Run tests working-directory: ./fixlib diff --git a/Dockerfile.fixinventorybase b/Dockerfile.fixinventorybase index 5a228d61cd..d091226128 100644 --- a/Dockerfile.fixinventorybase +++ b/Dockerfile.fixinventorybase @@ -56,6 +56,7 @@ RUN . /build/jupyterlite-venv-python3/bin/activate && python -m jupyter lite bui WORKDIR /usr/src RUN . /usr/local/fix-venv-python3/bin/activate && pip install -r requirements-extra.txt RUN . /usr/local/fix-venv-python3/bin/activate && find plugins/ -maxdepth 1 -mindepth 1 -type d -print0 | xargs -0 python -m pip install ./fixlib ./fixcore ./fixworker ./fixmetrics ./fixshell +RUN . /usr/local/fix-venv-python3/bin/activate && python -m pip install fixinventory-plugin-vsphere # Install AWS CLI WORKDIR /usr/src diff --git a/README.md b/README.md index 604f04a349..b3d87f9816 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 🤖 Automate Tasks: Tedious tasks like rule enforcement, resource tagging, and cleanup can be [automated using jobs](https://inventory.fix.security/docs/concepts/automation). -Currently, Fix Inventory can collect [AWS](plugins/aws), [Google Cloud](plugins/gcp), [DigitalOcean](plugins/digitalocean), [VMWare Vsphere](plugins/vsphere), [OneLogin](plugins/onelogin), and [Slack](plugins/slack) resources. If the cloud you are using is not listed, it is easy to write your own collectors. An example can be found [here](plugins/example_collector). +Currently, Fix Inventory can collect [AWS](plugins/aws), [Google Cloud](plugins/gcp), [DigitalOcean](plugins/digitalocean), [VMWare vSphere](https://github.com/someengineering/fixinventory-plugin-vsphere), [OneLogin](plugins/onelogin), and [Slack](plugins/slack) resources. If the cloud you are using is not listed, it is easy to write your own collectors. An example can be found [here](plugins/example_collector). ## Getting started diff --git a/plugins/vsphere/LICENSE b/plugins/vsphere/LICENSE deleted file mode 100644 index 0a656a64d8..0000000000 --- a/plugins/vsphere/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2024 Some Engineering Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/plugins/vsphere/MANIFEST.in b/plugins/vsphere/MANIFEST.in deleted file mode 100644 index bb3ec5f0d4..0000000000 --- a/plugins/vsphere/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.md diff --git a/plugins/vsphere/README.md b/plugins/vsphere/README.md deleted file mode 100644 index 82b0955856..0000000000 --- a/plugins/vsphere/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# fix-plugin-vsphere -VMWare VSphere Collector Plugin for Fix (Alpha) - -## License -See [LICENSE](../../LICENSE) for details. diff --git a/plugins/vsphere/fix_plugin_vsphere/__init__.py b/plugins/vsphere/fix_plugin_vsphere/__init__.py deleted file mode 100644 index abd3ac923a..0000000000 --- a/plugins/vsphere/fix_plugin_vsphere/__init__.py +++ /dev/null @@ -1,190 +0,0 @@ -from datetime import datetime - -from fixlib.logger import log -from fixlib.config import Config -from fixlib.baseplugin import BaseCollectorPlugin -from fixlib.baseresources import InstanceStatus - -from .vsphere_client import get_vsphere_client -from .resources import ( - VSphereCluster, - VSphereInstance, - VSphereDataCenter, - VSphereTemplate, - VSphereHost, - VSphereESXiHost, - VSphereDataStore, - VSphereDataStoreCluster, - VSphereResourcePool, -) -from .config import VSphereConfig -from typing import Dict - -from pyVmomi import vim - - -class VSphereCollectorPlugin(BaseCollectorPlugin): - cloud = "vsphere" - instances_dict = {} - - def get_host(self) -> VSphereHost: - return VSphereHost(id=Config.vsphere.host) - - def get_keymap_from_vmlist(self, list_vm) -> VSphereCluster: - """ - resolve custom key ID into a dict with its name - """ - keyMap = {} - for key in list_vm[0].availableField: - keyMap[key.key] = key.name - - return keyMap - - def get_custom_attributes(self, vm, keymap): - """ - use custom attribute keymap to resolve key IDs into a dict and - assign custom value. - """ - attr = {} - for value in vm.value: - attr[str(keymap[value.key])] = str(value.value) - - return attr - - def resource_pool(self, resourcepool, predecessor): - rpObj = VSphereResourcePool(id=resourcepool._moId, name=resourcepool.name) - self.graph.add_resource(predecessor, rpObj) - log.debug(f"Found ResourcePool - {resourcepool._moId} - {resourcepool.name}") - for vm in resourcepool.vm: - self.graph.add_edge(rpObj, self.instances_dict[vm._moId]) - - for successorPool in resourcepool.resourcePool: - log.debug(f"Found nested ResourcePool - {successorPool._moId} - {successorPool.name}") - self.resource_pool(successorPool, rpObj) - - def get_instances(self) -> None: - """ - loop over VMs and add them as VSphereInstance to parent - """ - - content = get_vsphere_client().client.RetrieveContent() - - container = content.rootFolder # starting point to look into - view_type = [vim.VirtualMachine] # object types to look for - recursive = True # whether we should look into it recursively - container_view = content.viewManager.CreateContainerView(container, view_type, recursive) - - vms = container_view.view - - keys = self.get_keymap_from_vmlist(vms) - - instance_status_map: Dict[str, InstanceStatus] = { - "pending": InstanceStatus.BUSY, - "running": InstanceStatus.RUNNING, - "shutting-down": InstanceStatus.BUSY, - "terminated": InstanceStatus.TERMINATED, - "stopping": InstanceStatus.BUSY, - "notRunning": InstanceStatus.STOPPED, - } - # loop over the list of VMs - for list_vm in vms: - try: - tags = self.get_custom_attributes(list_vm, keys) - - try: - ctime = datetime.fromtimestamp(list_vm.config.createDate.timestamp()) - except AttributeError: - ctime = None - - if list_vm.config.template: - vm = VSphereTemplate(id=list_vm._moId, name=str(list_vm.name), ctime=ctime) - else: - vm = VSphereInstance( - id=list_vm._moId, - name=str(list_vm.name), - instance_cores=int(list_vm.config.hardware.numCPU), - instance_memory=int(list_vm.config.hardware.memoryMB / 1024), - tags=tags, - ctime=ctime, - instance_status=instance_status_map.get(list_vm.guest.guestState, InstanceStatus.UNKNOWN), - ) - except Exception: - log.exception(f"Error while collecting {list_vm}") - else: - log.debug(f"found {vm.id} - {vm}") - self.instances_dict[vm.id] = vm - self.graph.add_node(vm) - - def collect(self) -> None: - log.debug("plugin: collecting vsphere resources") - - if not Config.vsphere.host: - log.debug("no VSphere host given - skipping collection") - return - - host = self.get_host() - - self.get_instances() - log.debug(f"found {len(self.instances_dict)} instances and templates") - - self.graph.add_resource(self.graph.root, host) - - content = get_vsphere_client().client.RetrieveContent() - datacenters = [entity for entity in content.rootFolder.childEntity if hasattr(entity, "vmFolder")] - # datacenter are root folder objects - for dc in datacenters: - log.debug(f"Found datacenter - {dc._moId} - {dc.name}") - dcObj = VSphereDataCenter(id=dc._moId, name=dc.name) - self.graph.add_resource(host, dcObj) - - # get clusters in datacenter - for cluster in dc.hostFolder.childEntity: - log.debug(f"Found cluster - {cluster._moId} - {cluster.name}") - clusterObj = VSphereCluster(id=cluster._moId, name=cluster.name) - self.graph.add_resource(dcObj, clusterObj) - try: - rpool = cluster.resourcePool - self.resource_pool(rpool, clusterObj) - except Exception: - log.warning(f"Resourcepool error for cluster {cluster._moId} {cluster.name}") - - # get hosts from a cluster - for host in cluster.host: # - log.debug(f"Found host - {host._moId} - {host.name}") - hostObj = VSphereESXiHost(id=host._moId, name=host.name) - self.graph.add_resource(clusterObj, hostObj) - # get vms for each host and read from the vm list - for vm in host.vm: - if vm._moId in self.instances_dict: - vmObj = self.instances_dict[vm._moId] - log.debug( - f"lookup vm - {vm._moId} - {vmObj.name} and assign to host {host._moId} - {host.name}" - ) - self.graph.add_edge(hostObj, vmObj) - else: - log.warning(f"host {host._moId} - {host.name} reports {vm._moId} but instance not found") - - for datastore in dc.datastoreFolder.childEntity: - if datastore._wsdlName == "Datastore": - log.debug(f"Found Datastore - {datastore._moId} - {datastore.name}") - dsObj = VSphereDataStore(id=datastore._moId, name=datastore.name) - self.graph.add_resource(dcObj, dsObj) - for vm in datastore.vm: - vmObj = self.instances_dict[vm._moId] - self.graph.add_edge(dsObj, vmObj) - elif datastore._wsdlName == "StoragePod": - log.debug(f"Found DatastoreCluster - {datastore._moId} - {datastore.name}") - dsc = VSphereDataStoreCluster(id=datastore._moId, name=datastore.name) - self.graph.add_resource(dcObj, dsc) - for store in datastore.childEntity: - log.debug(f"Found DatastoreCluster Datastore - {store._moId} - {store.name}") - if store._wsdlName == "Datastore": - dsObj = VSphereDataStore(id=store._moId, name=store.name) - self.graph.add_resource(dcObj, dsObj) - for vm in store.vm: - vmObj = self.instances_dict[vm._moId] - self.graph.add_edge(dsObj, vmObj) - - @staticmethod - def add_config(config: Config) -> None: - config.add_config(VSphereConfig) diff --git a/plugins/vsphere/fix_plugin_vsphere/config.py b/plugins/vsphere/fix_plugin_vsphere/config.py deleted file mode 100644 index 3760a19e8e..0000000000 --- a/plugins/vsphere/fix_plugin_vsphere/config.py +++ /dev/null @@ -1,15 +0,0 @@ -from attrs import define, field -from typing import ClassVar, Optional - - -@define -class VSphereConfig: - kind: ClassVar[str] = "vsphere" - user: Optional[str] = field(default=None, metadata={"description": "User name"}) - password: Optional[str] = field(default=None, metadata={"description": "Password"}) - host: Optional[str] = field(default=None, metadata={"description": "Host name/address"}) - port: int = field(default=443, metadata={"description": "TCP port"}) - insecure: bool = field( - default=True, - metadata={"description": "Allow insecure connection. Do not verify certificates."}, - ) diff --git a/plugins/vsphere/fix_plugin_vsphere/resources.py b/plugins/vsphere/fix_plugin_vsphere/resources.py deleted file mode 100644 index 98876da1b0..0000000000 --- a/plugins/vsphere/fix_plugin_vsphere/resources.py +++ /dev/null @@ -1,208 +0,0 @@ -from fixlib.graph import Graph -import fixlib.logger -from fixlib.baseresources import ( - BaseResource, - BaseAccount, - BaseRegion, - BaseZone, - BaseInstance, -) -from attrs import define -from typing import ClassVar -from pyVmomi import vim -from .vsphere_client import get_vsphere_client, VSphereClient - -log = fixlib.logger.getLogger("fix." + __name__) - - -@define(eq=False, slots=False) -class VSphereHost(BaseAccount): - kind: ClassVar[str] = "vsphere_host" - _kind_display: ClassVar[str] = "vSphere Host" - _kind_description: ClassVar[str] = ( - "vSphere Host is a physical server that runs the VMware vSphere hypervisor," - " allowing for virtualization and management of multiple virtual machines." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereDataCenter(BaseRegion): - kind: ClassVar[str] = "vsphere_data_center" - _kind_display: ClassVar[str] = "vSphere Data Center" - _kind_description: ClassVar[str] = ( - "vSphere Data Center is a virtualization platform provided by VMware for" - " managing and organizing virtual resources in a data center environment." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereCluster(BaseZone): - kind: ClassVar[str] = "vsphere_cluster" - _kind_display: ClassVar[str] = "vSphere Cluster" - _kind_description: ClassVar[str] = ( - "A vSphere Cluster is a group of ESXi hosts that work together to provide" - " resource pooling and high availability for virtual machines." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereESXiHost(BaseResource): - kind: ClassVar[str] = "vsphere_esxi_host" - _kind_display: ClassVar[str] = "vSphere ESXi Host" - _kind_description: ClassVar[str] = ( - "vSphere ESXi Host is a virtualization platform by VMware which allows users" - " to run multiple virtual machines on a single physical server." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereDataStore(BaseResource): - kind: ClassVar[str] = "vsphere_datastore" - _kind_display: ClassVar[str] = "vSphere Datastore" - _kind_description: ClassVar[str] = ( - "vSphere Datastore is a storage abstraction layer used in VMware vSphere to" - " manage and store virtual machine files and templates." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereDataStoreCluster(BaseResource): - kind: ClassVar[str] = "vsphere_datastore_cluster" - _kind_display: ClassVar[str] = "vSphere Datastore Cluster" - _kind_description: ClassVar[str] = ( - "vSphere Datastore Cluster is a feature in VMware's virtualization platform" - " that allows users to combine multiple storage resources into a single" - " datastore cluster, providing advanced management and high availability for" - " virtual machine storage." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereResourcePool(BaseResource): - kind: ClassVar[str] = "vsphere_resource_pool" - _kind_display: ClassVar[str] = "vSphere Resource Pool" - _kind_description: ClassVar[str] = ( - "vSphere Resource Pool is a feature in VMware's vSphere virtualization" - " platform that allows for the efficient allocation and management of CPU," - " memory, and storage resources in a virtual datacenter." - ) - - def delete(self, graph: Graph) -> bool: - return NotImplemented - - -@define(eq=False, slots=False) -class VSphereResource: - kind: ClassVar[str] = "vsphere_resource" - kind_display: ClassVar[str] = "vSphere Resource" - kind_description: ClassVar[str] = ( - "vSphere is a virtualization platform by VMware that allows users to create," - " manage, and run virtual machines and other virtual infrastructure" - " components." - ) - - def _vsphere_client(self) -> VSphereClient: - return get_vsphere_client() - - -@define(eq=False, slots=False) -class VSphereInstance(BaseInstance, VSphereResource): - kind: ClassVar[str] = "vsphere_instance" - _kind_display: ClassVar[str] = "vSphere Instance" - _kind_description: ClassVar[str] = ( - "vSphere Instances are virtual servers in VMware's cloud infrastructure," - " enabling users to run applications on VMware's virtualization platform." - ) - - def _vm(self): - return self._vsphere_client().get_object([vim.VirtualMachine], self.name) - - def delete(self, graph: Graph) -> bool: - if self._vm() is None: - log.error(f"Could not find vm name {self.name} with id {self.id}") - - log.debug(f"Deleting resource {self.id} in account {self.account(graph).id} region {self.region(graph).id}") - - if self._vm().runtime.powerState == "poweredOn": - task = self._vm().PowerOffVM_Task() - self._vsphere_client().wait_for_tasks([task]) - log.debug(f"Task finished - state: {task.info.state}") - - log.info(f"Destroying VM {self.id} with name {self.name}") - task = self._vm().Destroy_Task() - self._vsphere_client().wait_for_tasks([task]) - log.debug(f"Task finished - state: {task.info.state}") - return True - - def update_tag(self, key, value) -> bool: - log.debug(f"Updating or setting tag {key}: {value} on resource {self.id}") - self._vm().setCustomValue(key, value) - return True - - def delete_tag(self, key) -> bool: - log.debug(f"Deleting tag {key} on resource {self.id}") - self._vm().setCustomValue(key, "") - return True - - -@define(eq=False, slots=False) -class VSphereTemplate(BaseResource, VSphereResource): - kind: ClassVar[str] = "vsphere_template" - _kind_display: ClassVar[str] = "vSphere Template" - _kind_description: ClassVar[str] = ( - "vSphere templates are pre-configured virtual machine images that can be used" - " to deploy and scale virtual infrastructure within the VMware vSphere" - " platform." - ) - - def _get_default_resource_pool(self) -> vim.ResourcePool: - return self._vsphere_client().get_object([vim.ResourcePool], "Resources") - - def _template(self): - return self._vsphere_client().get_object([vim.VirtualMachine], self.name) - - def delete(self, graph: Graph) -> bool: - if self._template() is None: - log.error(f"Could not find vm name {self.name} with id {self.id}") - - log.debug(f"Deleting resource {self.id} in account {self.account(graph).id} region {self.region(graph).id}") - - log.debug(f"Mark template {self.id} as vm") - try: - self._template().MarkAsVirtualMachine(host=None, pool=self._get_default_resource_pool()) - except vim.fault.NotFound: - log.warning(f"Template {self.name} ({self.id}) not found - expecting we're done") - return True - except Exception as e: - log.exception(f"Unexpected error: {e}") - return False - - log.info(f"Destroying Template {self.id} with name {self.name}") - task = self._template().Destroy_Task() - self._vsphere_client().wait_for_tasks([task]) - log.debug(f"Task finished - state: {task.info.state}") - return True - - def update_tag(self, key, value) -> bool: - return NotImplemented - - def delete_tag(self, key) -> bool: - return NotImplemented diff --git a/plugins/vsphere/fix_plugin_vsphere/vsphere_client.py b/plugins/vsphere/fix_plugin_vsphere/vsphere_client.py deleted file mode 100644 index b58cc3d1b3..0000000000 --- a/plugins/vsphere/fix_plugin_vsphere/vsphere_client.py +++ /dev/null @@ -1,119 +0,0 @@ -from fixlib.config import Config - -from pyVmomi import vim, vmodl -from pyVim.connect import SmartConnect - - -class VSphereClient: - def __init__(self, host, user, pwd, port=443, insecure=False): - self.host = host - self.user = user - self.pwd = pwd - self.port = port - self.insecure = insecure - self._client = None - - def connect(self) -> None: - if self.insecure: - self._client = SmartConnect( - host=self.host, - user=self.user, - pwd=self.pwd, - port=self.port, - disableSslCertValidation=True, - ) - else: - self._client = SmartConnect(host=self.host, user=self.user, pwd=self.pwd, port=self.port) - - @property - def client(self): - if self._client is None: - self.connect() - - return self._client - - # taken from: https://github.com/vmware/ - # pyvmomi-community-samples/blob/ - # 889a2fadcb24e6b1bc1e30caab66f1a41a950988/ - # samples/tools/pchelper.py#L146 - def list_objects(self, type, folder=None, recurse=True): - content = self.client.RetrieveContent() - - if folder is None: - folder = content.rootFolder - - container = content.viewManager.CreateContainerView(folder, type, recurse) - - return container.view - - def search_object(self, type, name, folder=None, recurse=True): - objects = self.list_objects(type, folder, recurse) - - for managed_object_ref in objects: - if managed_object_ref.name == name: - object = managed_object_ref - break - return object - - def get_object(self, type, name, folder=None, recurse=True): - object = self.search_object(type, name, folder, recurse) - - if not object: - raise RuntimeError("Managed Object " + name + " not found.") - return object - - # taken from: https://github.com/vmware/ - # pyvmomi-community-samples/blob/master/ - # samples/tools/tasks.py - def wait_for_tasks(self, tasks): - """Given the service instance and tasks, it returns after all the - tasks are complete""" - - property_collector = self.client.content.propertyCollector - task_list = [str(task) for task in tasks] - # Create filter - obj_specs = [vmodl.query.PropertyCollector.ObjectSpec(obj=task) for task in tasks] - property_spec = vmodl.query.PropertyCollector.PropertySpec(type=vim.Task, pathSet=[], all=True) - filter_spec = vmodl.query.PropertyCollector.FilterSpec() - filter_spec.objectSet = obj_specs - filter_spec.propSet = [property_spec] - pcfilter = property_collector.CreateFilter(filter_spec, True) - try: - version, state = None, None - # Loop looking for updates till the state moves to a completed state. - while task_list: - update = property_collector.WaitForUpdates(version) - for filter_set in update.filterSet: - for obj_set in filter_set.objectSet: - task = obj_set.obj - for change in obj_set.changeSet: - if change.name == "info": - state = change.val.state - elif change.name == "info.state": - state = change.val - else: - continue - - if not str(task) in task_list: - continue - - if state == vim.TaskInfo.State.success: - # Remove task from taskList - task_list.remove(str(task)) - elif state == vim.TaskInfo.State.error: - raise task.info.error - # Move to next version - version = update.version - finally: - if pcfilter: - pcfilter.Destroy() - - -def get_vsphere_client() -> VSphereClient: - return VSphereClient( - host=Config.vsphere.host, - user=Config.vsphere.user, - pwd=Config.vsphere.password, - port=Config.vsphere.port, - insecure=Config.vsphere.insecure, - ) diff --git a/plugins/vsphere/pyproject.toml b/plugins/vsphere/pyproject.toml deleted file mode 100644 index 6b986bf6e7..0000000000 --- a/plugins/vsphere/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[project] -name = "fixinventory-plugin-vsphere" -description = "Fix VSphere Collector Plugin" -version = "4.1.0" -authors = [{name="Some Engineering Inc."}] -license = {file="LICENSE"} -requires-python = ">=3.12" -classifiers = [ - # Current project status - "Development Status :: 4 - Beta", - # Audience - "Intended Audience :: System Administrators", - "Intended Audience :: Information Technology", - # License information - "License :: OSI Approved :: Apache Software License", - # Supported python versions - "Programming Language :: Python :: 3.12", - # Supported OS's - "Operating System :: POSIX :: Linux", - "Operating System :: Unix", - # Extra metadata - "Environment :: Console", - "Natural Language :: English", - "Topic :: Security", - "Topic :: Utilities", -] -readme = {file="README.md", content-type="text/markdown"} - -dependencies = [ - "fixinventorylib==4.1.0", - "pyvmomi", -] - -[project.entry-points."fix.plugins"] -vsphere = "fix_plugin_vsphere:VSphereCollectorPlugin" - -[project.urls] -Documentation = "https://inventory.fix.security" -Source = "https://github.com/someengineering/fix/tree/main/plugins/vsphere" - -[build-system] -requires = ["setuptools>=67.8.0", "wheel>=0.40.0", "build>=0.10.0"] -build-backend = "setuptools.build_meta" - - diff --git a/plugins/vsphere/setup.cfg b/plugins/vsphere/setup.cfg deleted file mode 100644 index 7ca6537935..0000000000 --- a/plugins/vsphere/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[options] -packages = find: -include_package_data = True -zip_safe = False - -[aliases] -test=pytest diff --git a/plugins/vsphere/test/test_config.py b/plugins/vsphere/test/test_config.py deleted file mode 100644 index 8c6140528f..0000000000 --- a/plugins/vsphere/test/test_config.py +++ /dev/null @@ -1,13 +0,0 @@ -from fixlib.config import Config -from fix_plugin_vsphere import VSphereCollectorPlugin - - -def test_config(): - config = Config("dummy", "dummy") - VSphereCollectorPlugin.add_config(config) - Config.init_default_config() - assert Config.vsphere.user is None - assert Config.vsphere.password is None - assert Config.vsphere.host is None - assert Config.vsphere.port == 443 - assert Config.vsphere.insecure is True diff --git a/plugins/vsphere/tox.ini b/plugins/vsphere/tox.ini deleted file mode 100644 index d66e08c6d3..0000000000 --- a/plugins/vsphere/tox.ini +++ /dev/null @@ -1,29 +0,0 @@ -[tox] -env_list = syntax, tests, black - -[flake8] -max-line-length=120 -exclude = .git,.tox,__pycache__,.idea,.pytest_cache -ignore=F403, F405, E722, N806, N813, E266, W503, E203 - -[pytest] -addopts= --cov=fix_plugin_vsphere -rs -vv --cov-report html -testpaths= test - -[testenv] -usedevelop = true -deps = - --editable=file:///{toxinidir}/../../fixlib - -r../../requirements-all.txt -# until this is fixed: https://github.com/pypa/setuptools/issues/3518 -setenv = - SETUPTOOLS_ENABLE_FEATURES = legacy-editable - -[testenv:syntax] -commands = flake8 --verbose - -[testenv:tests] -commands= pytest - -[testenv:black] -commands = black --line-length 120 --check --diff --target-version py39 . diff --git a/requirements-all.txt b/requirements-all.txt index 8f769cdf41..a63f6a4fea 100644 --- a/requirements-all.txt +++ b/requirements-all.txt @@ -151,7 +151,6 @@ pytest-runner==6.0.1 python-arango==8.1.2 python-dateutil==2.9.0.post0 pytz==2024.1 -pyvmomi==8.0.3.0.1 pyyaml==6.0.2 requests==2.32.3 requests-oauthlib==2.0.0 diff --git a/requirements-extra.txt b/requirements-extra.txt index 8b3e4bd279..40978745fe 100644 --- a/requirements-extra.txt +++ b/requirements-extra.txt @@ -122,7 +122,6 @@ pyparsing==3.1.4 python-arango==8.1.2 python-dateutil==2.9.0.post0 pytz==2024.1 -pyvmomi==8.0.3.0.1 pyyaml==6.0.2 requests==2.32.3 requests-oauthlib==2.0.0 diff --git a/requirements.txt b/requirements.txt index 9bb7f9bc4f..3798c68d88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -110,7 +110,6 @@ pyparsing==3.1.4 python-arango==8.1.2 python-dateutil==2.9.0.post0 pytz==2024.2 -pyvmomi==8.0.3.0.1 pyyaml==6.0.2 requests==2.32.3 requests-oauthlib==2.0.0 diff --git a/setup_venv.sh b/setup_venv.sh index aa000cd146..94cf0cf9ac 100755 --- a/setup_venv.sh +++ b/setup_venv.sh @@ -180,7 +180,7 @@ install_fixinventory() { } install_plugins() { - local collector_plugins=(aws azure digitalocean dockerhub example_collector gcp github k8s onelogin onprem posthog random scarf slack vsphere) + local collector_plugins=(aws azure digitalocean dockerhub example_collector gcp github k8s onelogin onprem posthog random scarf slack) for plugin in "${collector_plugins[@]}"; do pip_install "$plugin" true done