diff --git a/ci/roles/baremetal_node/defaults/main.yml b/ci/roles/baremetal_node/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d993fcdb787b2d65658c7e9e70fbf810ff614f49
--- /dev/null
+++ b/ci/roles/baremetal_node/defaults/main.yml
@@ -0,0 +1,56 @@
+expected_fields:
+  - allocation_id
+  - bios_interface
+  - boot_interface
+  - boot_mode
+  - chassis_id
+  - clean_step
+  - conductor
+  - conductor_group
+  - console_interface
+  - created_at
+  - deploy_interface
+  - deploy_step
+  - driver
+  - driver_info
+  - driver_internal_info
+  - extra
+  - fault
+  - id
+  - inspect_interface
+  - instance_id
+  - instance_info
+  - is_automated_clean_enabled
+  - is_console_enabled
+  - is_maintenance
+  - is_protected
+  - is_retired
+  - is_secure_boot
+  - last_error
+  - links
+  - maintenance_reason
+  - management_interface
+  - name
+  - network_interface
+  - owner
+  - port_groups
+  - ports
+  - power_interface
+  - power_state
+  - properties
+  - protected_reason
+  - provision_state
+  - raid_config
+  - raid_interface
+  - rescue_interface
+  - reservation
+  - resource_class
+  - retired_reason
+  - states
+  - storage_interface
+  - target_power_state
+  - target_provision_state
+  - target_raid_config
+  - traits
+  - updated_at
+  - vendor_interface
diff --git a/ci/roles/baremetal_node/tasks/main.yml b/ci/roles/baremetal_node/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..367feca5436c321d000eeea29c6d5151dcd603dc
--- /dev/null
+++ b/ci/roles/baremetal_node/tasks/main.yml
@@ -0,0 +1,74 @@
+---
+# TODO: Actually run this role in CI. Atm we do not have DevStack's ironic plugin enabled.
+- name: Create baremetal node
+  openstack.cloud.baremetal_node:
+    cloud: "{{ cloud }}"
+    driver_info:
+      ipmi_address: "1.2.3.4"
+      ipmi_username: "admin"
+      ipmi_password: "secret"
+    name: ansible_baremetal_node
+    nics:
+      - mac: "aa:bb:cc:aa:bb:cc"
+    state: present
+  register: node
+
+- debug: var=node
+
+- name: assert return values of baremetal_node module
+  assert:
+    that:
+      # allow new fields to be introduced but prevent fields from being removed
+      - expected_fields|difference(node.node.keys())|length == 0
+
+- name: Fetch baremetal nodes
+  openstack.cloud.baremetal_node_info:
+    cloud: "{{ cloud }}"
+  register: nodes
+
+- name: assert module results of baremetal_node_info module
+  assert:
+    that:
+      - nodes.nodes|list|length > 0
+
+- name: assert return values of baremetal_node_info module
+  assert:
+    that:
+      # allow new fields to be introduced but prevent fields from being removed
+      - expected_fields|difference(nodes.nodes.0.keys())|length == 0
+
+- name: Fetch baremetal node by name
+  openstack.cloud.baremetal_node_info:
+    cloud: "{{ cloud }}"
+    name: ansible_baremetal_node
+  register: nodes
+
+- name: assert module results of baremetal_node_info module
+  assert:
+    that:
+      - nodes.nodes|list|length == 1
+      - nodes.nodes.0.id == node.node.id
+      - nodes.nodes.0.name == "ansible_baremetal_node"
+
+- name: Delete baremetal node
+  openstack.cloud.baremetal_node:
+    cloud: "{{ cloud }}"
+    driver_info:
+      ipmi_address: "1.2.3.4"
+      ipmi_username: "admin"
+      ipmi_password: "secret"
+    name: ansible_baremetal_node
+    nics:
+      - mac: "aa:bb:cc:aa:bb:cc"
+    state: absent
+
+- name: Fetch baremetal node by name
+  openstack.cloud.baremetal_node_info:
+    cloud: "{{ cloud }}"
+    name: ansible_baremetal_node
+  register: nodes
+
+- name: Assert that baremetal node has been deleted
+  assert:
+    that:
+      - nodes.nodes|list|length == 0
diff --git a/plugins/modules/baremetal_node.py b/plugins/modules/baremetal_node.py
index a1d51bf05a5aa1d0f97816cf5c0b4654be5a1456..245f9ef77b293576fc1808d68cac8b6a0ae5fa44 100644
--- a/plugins/modules/baremetal_node.py
+++ b/plugins/modules/baremetal_node.py
@@ -4,7 +4,7 @@
 # (c) 2014, Hewlett-Packard Development Company, L.P.
 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 
-DOCUMENTATION = '''
+DOCUMENTATION = r'''
 ---
 module: baremetal_node
 short_description: Create/Delete Bare Metal Resources from OpenStack
@@ -12,105 +12,69 @@ author: OpenStack Ansible SIG
 description:
     - Create or Remove Ironic nodes from OpenStack.
 options:
-    state:
-      description:
-        - Indicates desired state of the resource
-      choices: ['present', 'absent']
-      default: present
-      type: str
-    uuid:
-      description:
-        - globally unique identifier (UUID) to be given to the resource. Will
-          be auto-generated if not specified, and name is specified.
-        - Definition of a UUID will always take precedence to a name value.
-      type: str
-    name:
+    bios_interface:
       description:
-        - unique name identifier to be given to the resource.
+        - The bios interface for this node, e.g. C(no-bios).
       type: str
-    driver:
+    boot_interface:
       description:
-        - The name of the Ironic Driver to use with this node.
-        - Required when I(state=present)
+        - The boot interface for this node, e.g. C(pxe).
       type: str
-    chassis_uuid:
+    chassis_id:
       description:
         - Associate the node with a pre-defined chassis.
       type: str
-    ironic_url:
-      description:
-        - If noauth mode is utilized, this is required to be set to the
-          endpoint URL for the Ironic API.  Use with "auth" and "auth_type"
-          settings set to None.
-      type: str
-    resource_class:
+      aliases: ['chassis_uuid']
+    console_interface:
       description:
-        - The specific resource type to which this node belongs.
+        - The console interface for this node, e.g. C(no-console).
       type: str
-    bios_interface:
+    deploy_interface:
       description:
-        - The bios interface for this node, e.g. "no-bios".
+        - The deploy interface for this node, e.g. C(iscsi).
       type: str
-    boot_interface:
+    driver:
       description:
-        - The boot interface for this node, e.g. "pxe".
+        - The name of the Ironic Driver to use with this node.
+        - Required when I(state) is C(present)
       type: str
-    console_interface:
+    driver_info:
       description:
-        - The console interface for this node, e.g. "no-console".
-      type: str
-    deploy_interface:
+        - Information for this node's driver. Will vary based on which
+          driver is in use. Any sub-field which is populated will be validated
+          during creation. For compatibility reasons sub-fields `power`,
+          `deploy`, `management` and `console` are flattened.
+      required: true
+      type: dict
+    id:
       description:
-        - The deploy interface for this node, e.g. "iscsi".
+        - ID to be given to the baremetal node. Will be auto-generated on
+          creation if not specified, and I(name) is specified.
+        - Definition of I(id) will always take precedence over I(name).
       type: str
+      aliases: ['uuid']
     inspect_interface:
       description:
-        - The interface used for node inspection, e.g. "no-inspect".
+        - The interface used for node inspection, e.g. C(no-inspect).
       type: str
     management_interface:
       description:
         - The interface for out-of-band management of this node, e.g.
           "ipmitool".
       type: str
+    name:
+      description:
+        - unique name identifier to be given to the resource.
+      type: str
     network_interface:
       description:
         - The network interface provider to use when describing
           connections for this node.
       type: str
-    power_interface:
-      description:
-        - The interface used to manage power actions on this node, e.g.
-          "ipmitool".
-      type: str
-    raid_interface:
-      description:
-        - Interface used for configuring raid on this node.
-      type: str
-    rescue_interface:
-      description:
-        - Interface used for node rescue, e.g. "no-rescue".
-      type: str
-    storage_interface:
-      description:
-        - Interface used for attaching and detaching volumes on this node, e.g.
-          "cinder".
-      type: str
-    vendor_interface:
-      description:
-        - Interface for all vendor-specific actions on this node, e.g.
-          "no-vendor".
-      type: str
-    driver_info:
-      description:
-        - Information for this server's driver. Will vary based on which
-          driver is in use. Any sub-field which is populated will be validated
-          during creation. For compatibility reasons sub-fields `power`,
-          `deploy`, `management` and `console` are flattened.
-      required: true
-      type: dict
     nics:
       description:
-        - 'A list of network interface cards, eg, " - mac: aa:bb:cc:aa:bb:cc"'
+        - 'A list of network interface cards, eg, C( - mac: aa:bb:cc:aa:bb:cc)'
+        - This node attribute cannot be updated.
       required: true
       type: list
       elements: dict
@@ -119,316 +83,609 @@ options:
             description: The MAC address of the network interface card.
             type: str
             required: true
+    power_interface:
+      description:
+        - The interface used to manage power actions on this node, e.g.
+          C(ipmitool).
+      type: str
     properties:
       description:
-        - Definition of the physical characteristics of this server, used for scheduling purposes
+        - Definition of the physical characteristics of this node
+        - Used for scheduling purposes
       type: dict
       suboptions:
         cpu_arch:
           description:
             - CPU architecture (x86_64, i686, ...)
-          default: x86_64
+          type: str
         cpus:
           description:
             - Number of CPU cores this machine has
-          default: 1
-        ram:
+          type: str
+        memory_mb:
           description:
-            - amount of RAM this machine has, in MB
-          default: 1
-        disk_size:
+            - Amount of RAM  in MB this machine has
+          aliases: ['ram']
+          type: str
+        local_gb:
           description:
-            - size of first storage device in this machine (typically /dev/sda), in GB
-          default: 1
+            - Size in GB of first storage device in this machine (typically
+              /dev/sda)
+          aliases: ['disk_size']
+          type: str
         capabilities:
           description:
-            - special capabilities for the node, such as boot_option, node_role etc
-              (see U(https://docs.openstack.org/ironic/latest/install/advanced.html)
-              for more information)
-          default: ""
+            - Special capabilities for this node such as boot_option etc.
+            - For more information refer to
+              U(https://docs.openstack.org/ironic/latest/install/advanced.html).
+          type: str
         root_device:
           description:
             - Root disk device hints for deployment.
-            - See U(https://docs.openstack.org/ironic/latest/install/advanced.html#specifying-the-disk-for-deployment-root-device-hints)
-              for allowed hints.
-          default: ""
+            - For allowed hints refer to
+              U(https://docs.openstack.org/ironic/latest/install/advanced.html).
+          type: dict
+    raid_interface:
+      description:
+        - Interface used for configuring raid on this node.
+      type: str
+    rescue_interface:
+      description:
+        - Interface used for node rescue, e.g. C(no-rescue).
+      type: str
+    resource_class:
+      description:
+        - The specific resource type to which this node belongs.
+      type: str
     skip_update_of_masked_password:
       description:
-        - Allows the code that would assert changes to nodes to skip the
-          update if the change is a single line consisting of the password
-          field.
-        - As of Kilo, by default, passwords are always masked to API
-          requests, which means the logic as a result always attempts to
-          re-assert the password field.
+        - Deprecated, no longer used.
+        - Updating or specifing a password has not been supported for a while.
       type: bool
-    wait:
+    state:
       description:
-        - A boolean value instructing the module to wait for the newly created
-          node to reach the available state.
-      type: bool
-      default: 'no'
+        - Indicates desired state of the resource
+      choices: ['present', 'absent']
+      default: present
+      type: str
+    storage_interface:
+      description:
+        - Interface used for attaching and detaching volumes on this node, e.g.
+          C(cinder).
+      type: str
     timeout:
       description:
-        - An integer value representing the number of seconds to
-          wait for the newly created node to reach the available state.
-      default: 1800
+        - Number of seconds to wait for the newly created node to reach the
+          available state.
       type: int
+      default: 1800
+    vendor_interface:
+      description:
+        - Interface for all vendor-specific actions on this node, e.g.
+          C(no-vendor).
+      type: str
 requirements:
     - "python >= 3.6"
     - "openstacksdk"
-    - "jsonpatch"
 
 extends_documentation_fragment:
 - openstack.cloud.openstack
 '''
 
-EXAMPLES = '''
-# Enroll a node with some basic properties and driver info
-- openstack.cloud.baremetal_node:
+EXAMPLES = r'''
+- name: Enroll a node with some basic properties and driver info
+  openstack.cloud.baremetal_node:
+    chassis_id: "00000000-0000-0000-0000-000000000001"
     cloud: "devstack"
     driver: "pxe_ipmitool"
-    uuid: "00000000-0000-0000-0000-000000000002"
-    properties:
-      cpus: 2
-      cpu_arch: "x86_64"
-      ram: 8192
-      disk_size: 64
-      capabilities: "boot_option:local"
-      root_device:
-        wwn: "0x4000cca77fc4dba1"
-    nics:
-      - mac: "aa:bb:cc:aa:bb:cc"
-      - mac: "dd:ee:ff:dd:ee:ff"
     driver_info:
       ipmi_address: "1.2.3.4"
       ipmi_username: "admin"
       ipmi_password: "adminpass"
-    chassis_uuid: "00000000-0000-0000-0000-000000000001"
-
+    id: "00000000-0000-0000-0000-000000000002"
+    nics:
+      - mac: "aa:bb:cc:aa:bb:cc"
+      - mac: "dd:ee:ff:dd:ee:ff"
+    properties:
+      capabilities: "boot_option:local"
+      cpu_arch: "x86_64"
+      cpus: 2
+      local_gb: 64
+      memory_mb: 8192
+      root_device:
+        wwn: "0x4000cca77fc4dba1"
 '''
 
-try:
-    import jsonpatch
-    HAS_JSONPATCH = True
-except ImportError:
-    HAS_JSONPATCH = False
-
+RETURN = r'''
+node:
+    description: Dictionary describing the Bare Metal node.
+    type: dict
+    returned: On success when I(state) is 'present'.
+    contains:
+        allocation_id:
+            description: The UUID of the allocation associated with the node.
+                         If not null, will be the same as instance_id (the
+                         opposite is not always true). Unlike instance_id,
+                         this field is read-only. Please use the Allocation API
+                         to remove allocations.
+            returned: success
+            type: str
+        bios_interface:
+            description: The bios interface to be used for this node.
+            returned: success
+            type: str
+        boot_interface:
+            description: The boot interface for a node, e.g. "pxe".
+            returned: success
+            type: str
+        boot_mode:
+            description: The boot mode for a node, either "uefi" or "bios"
+            returned: success
+            type: str
+        chassis_id:
+            description: UUID of the chassis associated with this node. May be
+                         empty or None.
+            returned: success
+            type: str
+        clean_step:
+            description: The current clean step.
+            returned: success
+            type: str
+        conductor:
+            description: |
+                The conductor currently servicing a node.
+            returned: success
+            type: str
+        conductor_group:
+            description: The conductor group for a node.
+            returned: success
+            type: str
+        console_interface:
+            description: The console interface for a node, e.g. "no-console".
+            returned: success
+            type: str
+        created_at:
+            description: Bare Metal node created at timestamp.
+            returned: success
+            type: str
+        deploy_interface:
+            description: The deploy interface for a node, e.g. "direct".
+            returned: success
+            type: str
+        deploy_step:
+            description: The current deploy step.
+            returned: success
+            type: str
+        driver:
+            description: The name of the driver.
+            returned: success
+            type: str
+        driver_info:
+            description: All the metadata required by the driver to manage this
+                         node. List of fields varies between drivers, and can
+                         be retrieved from the
+                         /v1/drivers/<DRIVER_NAME>/properties resource.
+            returned: success
+            type: dict
+        driver_internal_info:
+            description: Internal metadata set and stored by the node's driver.
+            returned: success
+            type: dict
+        extra:
+            description: A set of one or more arbitrary metadata key and value
+                         pairs.
+            returned: success
+            type: dict
+        fault:
+            description: The fault indicates the active fault detected by
+                         ironic, typically the node is in "maintenance mode".
+                         None means no fault has been detected by ironic.
+                         "power failure" indicates ironic failed to retrieve
+                         power state from this node. There are other possible
+                         types, e.g., "clean failure" and "rescue abort
+                         failure".
+            returned: success
+            type: str
+        id:
+            description: The UUID for the resource.
+            returned: success
+            type: str
+        inspect_interface:
+            description: The interface used for node inspection.
+            returned: success
+            type: str
+        instance_id:
+            description: UUID of the Nova instance associated with this node.
+            returned: success
+            type: str
+        instance_info:
+            description: Information used to customize the deployed image. May
+                         include root partition size, a base 64 encoded config
+                         drive, and other metadata. Note that this field is
+                         erased automatically when the instance is deleted
+                         (this is done by requesting the node provision state
+                         be changed to DELETED).
+            returned: success
+            type: dict
+        is_automated_clean_enabled:
+            description: Indicates whether the node will perform automated
+                         clean or not.
+            returned: success
+            type: bool
+        is_console_enabled:
+            description: Indicates whether console access is enabled or
+                         disabled on this node.
+            returned: success
+            type: bool
+        is_maintenance:
+            description: Whether or not this node is currently in "maintenance
+                         mode". Setting a node into maintenance mode removes it
+                         from the available resource pool and halts some
+                         internal automation. This can happen manually (eg, via
+                         an API request) or automatically when Ironic detects a
+                         hardware fault that prevents communication with the
+                         machine.
+            returned: success
+            type: bool
+        is_protected:
+            description: Whether the node is protected from undeploying,
+                         rebuilding and deletion.
+            returned: success
+            type: bool
+        is_retired:
+            description: Whether the node is retired and can hence no longer be
+                         provided, i.e. move from manageable to available, and
+                         will end up in manageable after cleaning (rather than
+                         available).
+            returned: success
+            type: bool
+        is_secure_boot:
+            description: Indicates whether node is currently booted with
+                         secure_boot turned on.
+            returned: success
+            type: bool
+        last_error:
+            description: Any error from the most recent (last) transaction that
+                         started but failed to finish.
+            returned: success
+            type: str
+        links:
+            description: A list of relative links, including self and bookmark
+                         links.
+            returned: success
+            type: list
+        maintenance_reason:
+            description: User-settable description of the reason why this node
+                         was placed into maintenance mode
+            returned: success
+            type: str
+        management_interface:
+            description: Interface for out-of-band node management.
+            returned: success
+            type: str
+        name:
+            description: Human-readable identifier for the node resource. May
+                         be undefined. Certain words are reserved.
+            returned: success
+            type: str
+        network_interface:
+            description: Which Network Interface provider to use when plumbing
+                         the network connections for this node.
+            returned: success
+            type: str
+        owner:
+            description: A string or UUID of the tenant who owns the object.
+            returned: success
+            type: str
+        ports:
+            description: List of ironic ports on this node.
+            returned: success
+            type: list
+        port_groups:
+            description: List of ironic port groups on this node.
+            returned: success
+            type: list
+        power_interface:
+            description: Interface used for performing power actions on the
+                         node, e.g. "ipmitool".
+            returned: success
+            type: str
+        power_state:
+            description: The current power state of this node. Usually, "power
+                         on" or "power off", but may be "None" if Ironic is
+                         unable to determine the power state (eg, due to
+                         hardware failure).
+            returned: success
+            type: str
+        properties:
+            description: Physical characteristics of this node. Populated by
+                         ironic-inspector during inspection. May be edited via
+                         the REST API at any time.
+            returned: success
+            type: dict
+        protected_reason:
+            description: The reason the node is marked as protected.
+            returned: success
+            type: str
+        provision_state:
+            description: The current provisioning state of this node.
+            returned: success
+            type: str
+        raid_config:
+            description: Represents the current RAID configuration of the node.
+                         Introduced with the cleaning feature.
+            returned: success
+            type: dict
+        raid_interface:
+            description: Interface used for configuring RAID on this node.
+            returned: success
+            type: str
+        rescue_interface:
+            description: The interface used for node rescue, e.g. "no-rescue".
+            returned: success
+            type: str
+        reservation:
+            description: The name of an Ironic Conductor host which is holding
+                         a lock on this node, if a lock is held. Usually
+                         "null", but this field can be useful for debugging.
+            returned: success
+            type: str
+        resource_class:
+            description: A string which can be used by external schedulers to
+                         identify this node as a unit of a specific type of
+                         resource. For more details, see
+                         https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html
+            returned: success
+            type: str
+        retired_reason:
+            description: The reason the node is marked as retired.
+            returned: success
+            type: str
+        states:
+            description: Links to the collection of states.
+            returned: success
+            type: list
+        storage_interface:
+            description: Interface used for attaching and detaching volumes on
+                         this node, e.g. "cinder".
+            returned: success
+            type: str
+        target_power_state:
+            description: If a power state transition has been requested, this
+                         field represents the requested (ie, "target") state,
+                         either "power on" or "power off".
+            returned: success
+            type: str
+        target_provision_state:
+            description: If a provisioning action has been requested, this
+                         field represents the requested (ie, "target") state.
+                         Note that a node may go through several states during
+                         its transition to this target state. For instance,
+                         when requesting an instance be deployed to an
+                         AVAILABLE node, the node may go through the following
+                         state change progression, AVAILABLE -> DEPLOYING ->
+                         DEPLOYWAIT -> DEPLOYING -> ACTIVE
+            returned: success
+            type: str
+        target_raid_config:
+            description: Represents the requested RAID configuration of the
+                         node, which will be applied when the node next
+                         transitions through the CLEANING state. Introduced
+                         with the cleaning feature.
+            returned: success
+            type: dict
+        traits:
+            description: List of traits for this node.
+            returned: success
+            type: list
+        updated_at:
+            description: Bare Metal node updated at timestamp.
+            returned: success
+            type: str
+        vendor_interface:
+            description: Interface for vendor-specific functionality on this
+                         node, e.g. "no-vendor".
+            returned: success
+            type: str
+'''
 
-from ansible_collections.openstack.cloud.plugins.module_utils.ironic import (
-    IronicModule,
-    ironic_argument_spec,
-)
 from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
-    openstack_module_kwargs,
-    openstack_cloud_from_module
+    OpenStackModule
 )
 
 
-_PROPERTIES = {
-    'cpu_arch': 'cpu_arch',
-    'cpus': 'cpus',
-    'ram': 'memory_mb',
-    'disk_size': 'local_gb',
-    'capabilities': 'capabilities',
-    'root_device': 'root_device',
-}
-
-
-def _parse_properties(module):
-    """Convert ansible properties into native ironic values.
-
-    Also filter out any properties that are not set.
-    """
-    p = module.params['properties']
-    return {to_key: p[from_key] for (from_key, to_key) in _PROPERTIES.items()
-            if p.get(from_key) is not None}
-
-
-def _choose_id_value(module):
-    if module.params['uuid']:
-        return module.params['uuid']
-    if module.params['name']:
-        return module.params['name']
-    return None
-
-
-def _choose_if_password_only(module, patch):
-    if len(patch) == 1:
-        if 'password' in patch[0]['path'] and module.params['skip_update_of_masked_password']:
-            # Return false to abort update as the password appears
-            # to be the only element in the patch.
-            return False
-    return True
-
-
-def _exit_node_not_updated(module, server):
-    module.exit_json(
-        changed=False,
-        result="Node not updated",
-        uuid=server['uuid'],
-        provision_state=server['provision_state']
-    )
-
-
-def main():
-    argument_spec = ironic_argument_spec(
-        uuid=dict(),
-        name=dict(),
-        driver=dict(),
-        resource_class=dict(),
+class BaremetalNodeModule(OpenStackModule):
+    argument_spec = dict(
         bios_interface=dict(),
         boot_interface=dict(),
+        chassis_id=dict(aliases=['chassis_uuid']),
         console_interface=dict(),
         deploy_interface=dict(),
+        driver=dict(),
+        driver_info=dict(type='dict', required=True),
+        id=dict(aliases=['uuid']),
         inspect_interface=dict(),
         management_interface=dict(),
+        name=dict(),
         network_interface=dict(),
+        nics=dict(type='list', required=True, elements='dict'),
         power_interface=dict(),
+        properties=dict(
+            type='dict',
+            options=dict(
+                cpu_arch=dict(),
+                cpus=dict(),
+                memory_mb=dict(aliases=['ram']),
+                local_gb=dict(aliases=['disk_size']),
+                capabilities=dict(),
+                root_device=dict(type='dict'),
+            ),
+        ),
         raid_interface=dict(),
         rescue_interface=dict(),
+        resource_class=dict(),
+        skip_update_of_masked_password=dict(
+            type='bool',
+            removed_in_version='3.0.0',
+            removed_from_collection='openstack.cloud',
+        ),
+        state=dict(default='present', choices=['present', 'absent']),
         storage_interface=dict(),
+        timeout=dict(default=1800, type='int'),  # increased default value
         vendor_interface=dict(),
-        driver_info=dict(type='dict', required=True),
-        nics=dict(type='list', required=True, elements="dict"),
-        properties=dict(type='dict', default={}),
-        chassis_uuid=dict(),
-        skip_update_of_masked_password=dict(type='bool'),
-        state=dict(default='present', choices=['present', 'absent']),
-        wait=dict(type='bool', default=False),
-        timeout=dict(type='int', default=1800),
     )
-    module_kwargs = openstack_module_kwargs()
-    module = IronicModule(argument_spec, **module_kwargs)
-
-    if not HAS_JSONPATCH:
-        module.fail_json(msg='jsonpatch is required for this module')
-
-    node_id = _choose_id_value(module)
-
-    sdk, cloud = openstack_cloud_from_module(module)
-    try:
-        server = cloud.get_machine(node_id)
-        if module.params['state'] == 'present':
-            if module.params['driver'] is None:
-                module.fail_json(msg="A driver must be defined in order "
-                                     "to set a node to present.")
-
-            properties = _parse_properties(module)
-            driver_info = module.params['driver_info']
-            kwargs = dict(
-                driver=module.params['driver'],
-                properties=properties,
-                driver_info=driver_info,
-                name=module.params['name'],
-            )
-            optional_field_names = ('resource_class',
-                                    'bios_interface',
-                                    'boot_interface',
-                                    'console_interface',
-                                    'deploy_interface',
-                                    'inspect_interface',
-                                    'management_interface',
-                                    'network_interface',
-                                    'power_interface',
-                                    'raid_interface',
-                                    'rescue_interface',
-                                    'storage_interface',
-                                    'vendor_interface')
-            for i in optional_field_names:
-                if module.params[i]:
-                    kwargs[i] = module.params[i]
-
-            if module.params['chassis_uuid']:
-                kwargs['chassis_uuid'] = module.params['chassis_uuid']
-
-            if server is None:
-                # Note(TheJulia): Add a specific UUID to the request if
-                # present in order to be able to re-use kwargs for if
-                # the node already exists logic, since uuid cannot be
-                # updated.
-                if module.params['uuid']:
-                    kwargs['uuid'] = module.params['uuid']
-
-                server = cloud.register_machine(
-                    module.params['nics'],
-                    wait=module.params['wait'],
-                    timeout=module.params['timeout'],
-                    **kwargs)
-                module.exit_json(changed=True, uuid=server['uuid'],
-                                 provision_state=server['provision_state'])
-            else:
-                # TODO(TheJulia): Presently this does not support updating
-                # nics.  Support needs to be added.
-                #
-                # Note(TheJulia): This message should never get logged
-                # however we cannot realistically proceed if neither a
-                # name or uuid was supplied to begin with.
-                if not node_id:
-                    module.fail_json(msg="A uuid or name value "
-                                         "must be defined")
-
-                # Note(TheJulia): Constructing the configuration to compare
-                # against.  The items listed in the server_config block can
-                # be updated via the API.
-
-                server_config = dict(
-                    driver=server['driver'],
-                    properties=server['properties'],
-                    driver_info=server['driver_info'],
-                    name=server['name'],
-                )
-
-                # Add the pre-existing chassis_uuid only if
-                # it is present in the server configuration.
-                if hasattr(server, 'chassis_uuid'):
-                    server_config['chassis_uuid'] = server['chassis_uuid']
-
-                # Note(TheJulia): If a password is defined and concealed, a
-                # patch will always be generated and re-asserted.
-                patch = jsonpatch.JsonPatch.from_diff(server_config, kwargs)
-
-                if not patch:
-                    _exit_node_not_updated(module, server)
-                elif _choose_if_password_only(module, list(patch)):
-                    # Note(TheJulia): Normally we would allow the general
-                    # exception catch below, however this allows a specific
-                    # message.
-                    try:
-                        server = cloud.patch_machine(
-                            server['uuid'],
-                            list(patch))
-                    except Exception as e:
-                        module.fail_json(msg="Failed to update node, "
-                                         "Error: %s" % e.message)
-
-                    # Enumerate out a list of changed paths.
-                    change_list = []
-                    for change in list(patch):
-                        change_list.append(change['path'])
-                    module.exit_json(changed=True,
-                                     result="Node Updated",
-                                     changes=change_list,
-                                     uuid=server['uuid'],
-                                     provision_state=server['provision_state'])
-
-            # Return not updated by default as the conditions were not met
-            # to update.
-            _exit_node_not_updated(module, server)
-
-        if module.params['state'] == 'absent':
-            if not node_id:
-                module.fail_json(msg="A uuid or name value must be defined "
-                                     "in order to remove a node.")
-
-            if server is not None:
-                cloud.unregister_machine(module.params['nics'],
-                                         server['uuid'])
-                module.exit_json(changed=True, result="deleted")
-            else:
-                module.exit_json(changed=False, result="Server not found")
-
-    except sdk.exceptions.OpenStackCloudException as e:
-        module.fail_json(msg=str(e))
+
+    module_kwargs = dict(
+        required_if=[
+            ('state', 'present', ('driver',)),
+        ],
+        required_one_of=[
+            ('id', 'name'),
+        ],
+        supports_check_mode=True,
+    )
+
+    def run(self):
+        name_or_id = \
+            self.params['id'] if self.params['id'] else self.params['name']
+        node = self.conn.baremetal.find_node(name_or_id)
+        state = self.params['state']
+
+        if self.ansible.check_mode:
+            self.exit_json(changed=self._will_change(state, node))
+
+        if state == 'present' and not node:
+            node = self._create()
+            self.exit_json(changed=True,
+                           node=node.to_dict(computed=False))
+
+        elif state == 'present' and node:
+            update = self._build_update(node)
+            if update:
+                node = self._update(node, update)
+            self.exit_json(changed=bool(update),
+                           node=node.to_dict(computed=False))
+
+        elif state == 'absent' and node:
+            self._delete(node)
+            self.exit_json(changed=True)
+
+        elif state == 'absent' and not node:
+            self.exit_json(changed=False)
+
+    def _build_update(self, node):
+        update = {}
+        # TODO(TheJulia): Presently this does not support updating nics.
+        #                 Support needs to be added.
+
+        # Update all known updateable attributes
+        node_attributes = dict(
+            (k, self.params[k])
+            for k in [
+                'bios_interface',
+                'boot_interface',
+                'chassis_id',
+                'console_interface',
+                'deploy_interface',
+                'driver',
+                'driver_info',
+                'inspect_interface',
+                'management_interface',
+                'name',
+                'network_interface',
+                'power_interface',
+                'raid_interface',
+                'rescue_interface',
+                'resource_class',
+                'storage_interface',
+                'vendor_interface',
+            ]
+            if k in self.params and self.params[k] is not None
+            and self.params[k] != node[k])
+
+        properties = self.params['properties']
+        if properties is not None:
+            properties = dict(
+                (k, v) for k, v in properties.items() if v is not None)
+            if properties and properties != node['properties']:
+                node_attributes['properties'] = properties
+
+        # name can only be updated if id is given
+        if self.params['id'] is None and 'name' in node_attributes:
+            self.fail_json(msg='The name of a node cannot be updated without'
+                               ' specifying an id')
+
+        if node_attributes:
+            update['node_attributes'] = node_attributes
+
+        return update
+
+    def _create(self):
+        kwargs = {}
+
+        for k in ('bios_interface',
+                  'boot_interface',
+                  'chassis_id',
+                  'console_interface',
+                  'deploy_interface',
+                  'driver',
+                  'driver_info',
+                  'id',
+                  'inspect_interface',
+                  'management_interface',
+                  'name',
+                  'network_interface',
+                  'power_interface',
+                  'raid_interface',
+                  'rescue_interface',
+                  'resource_class',
+                  'storage_interface',
+                  'vendor_interface'):
+            if self.params[k] is not None:
+                kwargs[k] = self.params[k]
+
+        properties = self.params['properties']
+        if properties is not None:
+            properties = dict(
+                (k, v) for k, v in properties.items() if v is not None)
+            if properties:
+                kwargs['properties'] = properties
+
+        node = self.conn.register_machine(
+            nics=self.params['nics'],
+            wait=self.params['wait'],
+            timeout=self.params['timeout'],
+            **kwargs)
+
+        self.exit_json(changed=True, node=node.to_dict(computed=False))
+
+    def _delete(self, node):
+        self.conn.unregister_machine(
+            nics=self.params['nics'], uuid=node['id'])
+
+    def _update(self, node, update):
+        node_attributes = update.get('node_attributes')
+        if node_attributes:
+            node = self.conn.baremetal.update_node(
+                node['id'], **node_attributes)
+
+        return node
+
+    def _will_change(self, state, node):
+        if state == 'present' and not node:
+            return True
+        elif state == 'present' and node:
+            return bool(self._build_update(node))
+        elif state == 'absent' and node:
+            return False
+        else:
+            # state == 'absent' and not node:
+            return True
+
+
+def main():
+    module = BaremetalNodeModule()
+    module()
 
 
 if __name__ == "__main__":
diff --git a/plugins/modules/baremetal_node_info.py b/plugins/modules/baremetal_node_info.py
index 4f73cf00d13979f21cf28c8a2911a8980abcacbb..97ce5598c669081d59d9ed0c388129228461ff23 100644
--- a/plugins/modules/baremetal_node_info.py
+++ b/plugins/modules/baremetal_node_info.py
@@ -5,27 +5,22 @@
 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 
 
-DOCUMENTATION = '''
+DOCUMENTATION = r'''
 module: baremetal_node_info
 short_description: Retrieve information about Bare Metal nodes from OpenStack
 author: OpenStack Ansible SIG
 description:
     - Retrieve information about Bare Metal nodes from OpenStack.
 options:
-    node:
-      description:
-        - Name or globally unique identifier (UUID) to identify the host.
-      type: str
     mac:
       description:
-        - Unique mac address that is used to attempt to identify the host.
+        - MAC address that is used to attempt to identify the host.
       type: str
-    ironic_url:
+    name:
       description:
-        - If noauth mode is utilized, this is required to be set to the
-          endpoint URL for the Ironic API.  Use with "auth" and "auth_type"
-          settings set to None.
+        - Name or ID of the baremetal node.
       type: str
+      aliases: ['node']
 requirements:
     - "python >= 3.6"
     - "openstacksdk"
@@ -34,34 +29,36 @@ extends_documentation_fragment:
 - openstack.cloud.openstack
 '''
 
-EXAMPLES = '''
-# Gather information about all baremeal nodes
-- openstack.cloud.baremetal_node_info:
+EXAMPLES = r'''
+- name: Gather information about all baremeal nodes
+  openstack.cloud.baremetal_node_info:
     cloud: "devstack"
-  register: result
-- debug:
-    msg: "{{ result.baremetal_nodes }}"
-# Gather information about a baremeal node
-- openstack.cloud.baremetal_node_info:
+  register: nodes
+
+- debug: var=nodes
+
+- name: Gather information about a baremeal node
+  openstack.cloud.baremetal_node_info:
     cloud: "devstack"
-    node: "00000000-0000-0000-0000-000000000002"
-  register: result
-- debug:
-    msg: "{{ result.baremetal_nodes }}"
+    name: "00000000-0000-0000-0000-000000000002"
+  register: nodes
+
+- debug: var=nodes
 '''
 
-RETURN = '''
-baremetal_nodes:
-    description: Bare Metal node list. A subset of the dictionary keys
-                 listed below may be returned, depending on your cloud
-                 provider.
-    returned: always, but can be null
-    type: complex
+RETURN = r'''
+nodes:
+    description: |
+        Bare Metal node list. A subset of the dictionary keys listed below may
+        be returned, depending on your cloud provider.
+    returned: always
+    type: list
+    elements: dict
     contains:
         allocation_id:
             description: The UUID of the allocation associated with the node.
-                         If not null, will be the same as instance_uuid (the
-                         opposite is not always true). Unlike instance_uuid,
+                         If not null, will be the same as instance_id (the
+                         opposite is not always true). Unlike instance_id,
                          this field is read-only. Please use the Allocation API
                          to remove allocations.
             returned: success
@@ -88,14 +85,11 @@ baremetal_nodes:
             returned: success
             type: str
         conductor:
-            description: The conductor currently servicing a node. This field
-                         is read-only.
+            description: The conductor currently servicing a node.
             returned: success
             type: str
         conductor_group:
-            description: The conductor group for a node. Case-insensitive
-                         string up to 255 characters, containing a-z, 0-9, _,
-                         -, and ..
+            description: The conductor group for a node.
             returned: success
             type: str
         console_interface:
@@ -239,209 +233,10 @@ baremetal_nodes:
             description: List of ironic ports on this node.
             returned: success
             type: list
-            elements: dict
-            contains:
-              address:
-                description: Physical hardware address of this network port,
-                             typically the hardware MAC address.
-                returned: success
-                type: str
-              created_at:
-                description: The UTC date and time when the resource was
-                             created, ISO 8601 format.
-                returned: success
-                type: str
-              extra:
-                description: A set of one or more arbitrary metadata key and
-                             value pairs.
-                returned: success
-                type: dict
-              id:
-                description: The UUID for the resource.
-                returned: success
-                type: str
-              internal_info:
-                description: Internal metadata set and stored by the port. This
-                             field is read-only.
-                returned: success
-                type: dict
-              is_pxe_enabled:
-                description: Indicates whether PXE is enabled or disabled on
-                             the port.
-                returned: success
-                type: str
-              links:
-                  description: A list of relative links, including self and bookmark
-                               links.
-                  returned: success
-                  type: list
-              local_link_connection:
-                description: The port binding profile. If specified, must
-                             contain switch_id (only a MAC address or an
-                             OpenFlow based datapath_id of the switch are
-                             accepted in this field) and port_id (identifier of
-                             the physical port on the switch to which node's
-                             port is connected to) fields. switch_info is an
-                             optional string field to be used to store any
-                             vendor-specific information.
-                returned: success
-                type: dict
-              node_id:
-                description: UUID of the node this resource belongs to.
-                returned: success
-                type: str
-              physical_network:
-                description: The name of the physical network to which a port
-                             is connected. May be empty.
-                returned: success
-                type: str
-              port_group_id:
-                description: UUID of the port group this resource belongs to.
-                returned: success
-                type: str
-              updated_at:
-                description: The UTC date and time when the resource was
-                             updated, ISO 8601 format. May be "null".
-                returned: success
-                type: str
         port_groups:
             description: List of ironic port groups on this node.
             returned: success
             type: list
-            elements: dict
-            contains:
-              address:
-                description: Physical hardware address of this port group,
-                             typically the hardware MAC address.
-                returned: success
-                type: str
-              created_at:
-                description: The UTC date and time when the resource was
-                             created, ISO 8601 format.
-                returned: success
-                type: str
-              extra:
-                description: A set of one or more arbitrary metadata key and
-                             value pairs.
-                returned: success
-                type: dict
-              id:
-                description: The UUID for the resource.
-                returned: success
-                type: str
-              internal_info:
-                description: Internal metadata set and stored by the port group.
-                             This field is read-only.
-                returned: success
-                type: dict
-              is_standalone_ports_supported:
-                description: Indicates whether ports that are members of this
-                             port group can be used as stand-alone ports.
-                returned: success
-                type: bool
-              links:
-                  description: A list of relative links, including self and bookmark
-                               links.
-                  returned: success
-                  type: list
-              mode:
-                description: Mode of the port group. For possible values, refer
-                             to https://www.kernel.org/doc/Documentation/networking/bonding.txt.
-                             If not specified in a request to create a port
-                             group, it will be set to the value of the
-                             [DEFAULT]default_portgroup_mode configuration
-                             option. When set, can not be removed from the port
-                             group.
-                returned: success
-                type: str
-              name:
-                description: Human-readable identifier for the port group
-                             resource. May be undefined.
-                returned: success
-                type: str
-              node_id:
-                description: UUID of the node this resource belongs to.
-                returned: success
-                type: str
-              ports:
-                description: List of ports belonging to this port group.
-                returned: success
-                type: list
-                elements: dict
-                contains:
-                  address:
-                    description: Physical hardware address of this network port,
-                                 typically the hardware MAC address.
-                    returned: success
-                    type: str
-                  created_at:
-                    description: The UTC date and time when the resource was
-                                 created, ISO 8601 format.
-                    returned: success
-                    type: str
-                  extra:
-                    description: A set of one or more arbitrary metadata key and
-                                 value pairs.
-                    returned: success
-                    type: dict
-                  id:
-                    description: The UUID for the resource.
-                    returned: success
-                    type: str
-                  internal_info:
-                    description: Internal metadata set and stored by the port. This
-                                 field is read-only.
-                    returned: success
-                    type: dict
-                  is_pxe_enabled:
-                    description: Indicates whether PXE is enabled or disabled on
-                                 the port.
-                    returned: success
-                    type: str
-                  links:
-                      description: A list of relative links, including self and bookmark
-                                   links.
-                      returned: success
-                      type: list
-                  local_link_connection:
-                    description: The port binding profile. If specified, must
-                                 contain switch_id (only a MAC address or an
-                                 OpenFlow based datapath_id of the switch are
-                                 accepted in this field) and port_id (identifier of
-                                 the physical port on the switch to which node's
-                                 port is connected to) fields. switch_info is an
-                                 optional string field to be used to store any
-                                 vendor-specific information.
-                    returned: success
-                    type: dict
-                  node_id:
-                    description: UUID of the node this resource belongs to.
-                    returned: success
-                    type: str
-                  physical_network:
-                    description: The name of the physical network to which a port
-                                 is connected. May be empty.
-                    returned: success
-                    type: str
-                  port_group_id:
-                    description: UUID of the port group this resource belongs to.
-                    returned: success
-                    type: str
-                  updated_at:
-                    description: The UTC date and time when the resource was
-                                 updated, ISO 8601 format. May be "null".
-                    returned: success
-                    type: str
-              properties:
-                description: Key/value properties related to the port group's
-                             configuration.
-                returned: success
-                type: dict
-              updated_at:
-                description: The UTC date and time when the resource was
-                             updated, ISO 8601 format. May be "null".
-                returned: success
-                type: str
         power_interface:
             description: Interface used for performing power actions on the
                          node, e.g. "ipmitool".
@@ -544,73 +339,74 @@ baremetal_nodes:
                          node, e.g. "no-vendor".
             returned: success
             type: str
+baremetal_nodes:
+    description: Same as C(nodes), kept for backward compatibility.
+    returned: always
+    type: list
+    elements: dict
 '''
 
-
-from ansible_collections.openstack.cloud.plugins.module_utils.ironic import (
-    IronicModule,
-    ironic_argument_spec,
-)
 from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
-    openstack_module_kwargs,
-    openstack_cloud_from_module
+    OpenStackModule
 )
 
 
-def get_ports_and_portgroups(cloud, machine):
-    machine['ports'] = [nic.to_dict(computed=False)
-                        for nic in cloud.baremetal.ports(
-                            details=True, node_id=machine['id'])]
-
-    machine['port_groups'] = [grp.to_dict(computed=False) for grp in
-                              cloud.baremetal.port_groups(node=machine['id'],
-                                                          details=True)]
-
-    # links to ports are not useful, replace with list of ports
-    for port_group in machine['port_groups']:
-        port_group['ports'] = [port for port in machine['ports']
-                               if port['port_group_id'] == port_group['id']]
-
-
-def main():
-    argument_spec = ironic_argument_spec(
-        node=dict(),
+class BaremetalNodeInfoModule(OpenStackModule):
+    argument_spec = dict(
         mac=dict(),
+        name=dict(aliases=['node']),
     )
-    module_kwargs = openstack_module_kwargs()
-    module_kwargs['supports_check_mode'] = True
 
-    module = IronicModule(argument_spec, **module_kwargs)
+    module_kwargs = dict(
+        mutually_exclusive=[
+            ('mac', 'name'),
+        ],
+        supports_check_mode=True,
+    )
 
-    machine = None
-    machines = list()
+    def run(self):
+        name_or_id = self.params['name']
+        mac = self.params['mac']
 
-    sdk, cloud = openstack_cloud_from_module(module)
-    try:
-        if module.params['node']:
-            machine = cloud.baremetal.find_node(module.params['node'])
-        elif module.params['mac']:
-            nic = next(cloud.baremetal.ports(address=module.params['mac'],
-                                             fields=['node_id']), None)
-            if nic:
-                machine = cloud.baremetal.find_node(nic['node_id'])
+        node_id = None
+        if name_or_id:
+            # self.conn.baremetal.nodes() does not support searching by name or
+            # id which we want to provide for backward compatibility
+            node = self.conn.baremetal.find_node(name_or_id)
+            if node:
+                node_id = node['id']
+        elif mac:
+            # self.conn.get_machine_by_mac(mac) is not necessary
+            # because nodes can be filtered by instance_id
+            baremetal_port = self.conn.get_nic_by_mac(mac)
+            if baremetal_port:
+                node_id = baremetal_port['node_id']
 
-        # Fail if node not found
-        if (module.params['node'] or module.params['mac']) and not machine:
-            module.fail_json(msg='The baremetal node was not found')
+        if name_or_id or mac:
+            if node_id:
+                # fetch node details with self.conn.baremetal.get_node()
+                # because self.conn.baremetal.nodes() does not provide a
+                # query parameter to filter by a node's id
+                node = self.conn.baremetal.get_node(node_id)
+                nodes = [node.to_dict(computed=False)]
+            else:  # not node_id
+                # return empty list when no matching node could be found
+                # because *_info modules do not raise errors on missing
+                # resources
+                nodes = []
+        else:  # not name_or_id and not mac
+            nodes = [node.to_dict(computed=False) for node in
+                     self.conn.baremetal.nodes(details=True)]
 
-        if machine:
-            machines.append(machine.to_dict(computed=False))
-        else:
-            machines = [machine.to_dict(computed=False)
-                        for machine in cloud.baremetal.nodes(details=True)]
+        self.exit_json(changed=False,
+                       nodes=nodes,
+                       # keep for backward compatibility
+                       baremetal_nodes=nodes)
 
-        for machine in machines:
-            get_ports_and_portgroups(cloud, machine)
 
-        module.exit_json(changed=False, baremetal_nodes=machines)
-    except sdk.exceptions.OpenStackCloudException as e:
-        module.fail_json(msg=str(e))
+def main():
+    module = BaremetalNodeInfoModule()
+    module()
 
 
 if __name__ == "__main__":