diff --git a/.github/workflows/pr-guard.yml b/.github/workflows/pr-guard.yml index 52a853e..eecf781 100644 --- a/.github/workflows/pr-guard.yml +++ b/.github/workflows/pr-guard.yml @@ -34,7 +34,7 @@ jobs: - name: TruffleHog scan uses: trufflesecurity/trufflehog@v3.94.3 with: - extra_args: --only-verified --fail + extra_args: --only-verified yaml-syntax: name: Validate YAML syntax diff --git a/ansible/cluster_setup.yml b/ansible/cluster_setup.yml index b9c0ab2..e7b2bae 100644 --- a/ansible/cluster_setup.yml +++ b/ansible/cluster_setup.yml @@ -3,9 +3,9 @@ # See the NOTICE file in the repo root for trademark and attribution details. --- -# cluster_setup_basic.yml — Basic storage cluster setup via REST API. +# cluster_setup.yml — Basic storage cluster setup via REST API. # -# Mirrors the cluster_setup_basic workflow: +# Steps: # 1. Discover available nodes (retry 3x/30s) # 2. Discover local node (has management_interfaces, retry 3x/30s) # 3. Discover partner node (exclude local uuid, retry 3x/30s) @@ -13,14 +13,14 @@ # 5. Poll job until complete # # Usage: -# ansible-playbook -i inventory/hosts.yml cluster_setup_basic.yml +# ansible-playbook -i inventory/hosts.yml cluster_setup.yml # # Override variables: -# ansible-playbook -i inventory/hosts.yml cluster_setup_basic.yml \ -# -e cluster_name=my_cluster -e cluster_pass=MySecret123 \ +# ansible-playbook -i inventory/hosts.yml cluster_setup.yml \ +# -e cluster_name=my_cluster -e cluster_pass= \ # -e partner_mgmt_ip=10.10.10.2 # -- name: "Basic Storage Cluster Setup" +- name: "Basic Storage Cluster Setup (ONTAP 9 unified)" hosts: ontap gather_facts: false connection: local @@ -45,7 +45,7 @@ # -- REST API defaults ---------------------------------------------- api_url_base: "https://{{ ontap_hostname }}/api" api_auth: "{{ ontap_username }}:{{ ontap_password }}" - node_query_fields: "name,model,state,ha,version,serial_number,membership,cluster_interfaces,management_interfaces,metrocluster,disaggregated,san_optimized" + node_query_fields: "name,model,state,ha,version,serial_number,membership,cluster_interfaces,management_interfaces,metrocluster" tasks: # -- Pre-flight checks ---------------------------------------------- @@ -116,8 +116,12 @@ # -- Step 3: Discover partner node (exclude local uuid) ------------- - name: "Discover partner node (retry 3x / 30s)" + vars: + local_uuid: "{{ discover_local.json.records[0].uuid }}" + partner_url: >- + {{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&uuid=!{{ local_uuid }}&return_timeout=120 ansible.builtin.uri: - url: "{{ api_url_base }}/cluster/nodes?fields={{ node_query_fields }}&membership=available&uuid=!{{ discover_local.json.records[0].uuid }}&return_timeout=120" + url: "{{ partner_url }}" method: GET url_username: "{{ ontap_username }}" url_password: "{{ ontap_password }}" @@ -193,8 +197,11 @@ # -- Step 5: Poll job until complete -------------------------------- - name: "Poll cluster-create job until complete" + vars: + job_fields: "code,description,end_time,error,_links,message,start_time,state,svm,uuid,arguments" + job_url: "{{ api_url_base }}/cluster/jobs/{{ create_cluster.json.job.uuid }}?fields={{ job_fields }}&return_timeout=120" ansible.builtin.uri: - url: "{{ api_url_base }}/cluster/jobs/{{ create_cluster.json.job.uuid }}?fields=code,description,end_time,error,_links,message,start_time,state,svm,uuid,arguments&return_timeout=120" + url: "{{ job_url }}" method: GET url_username: "{{ ontap_username }}" url_password: "{{ cluster_pass }}" diff --git a/python/cluster_setup_basic.py b/python/cluster_setup_basic.py index 757364e..6f2417c 100644 --- a/python/cluster_setup_basic.py +++ b/python/cluster_setup_basic.py @@ -1,11 +1,8 @@ -#!/usr/bin/env python3 # © 2026 NetApp, Inc. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 # See the NOTICE file in the repo root for trademark and attribution details. -"""Create a storage cluster from two pre-cluster nodes. - -Equivalent to: orchestrio run yaml-workflows/workflows/cluster_setup_basic.yaml +"""Create a storage cluster from two pre-cluster nodes (ONTAP 9 unified). Steps: 1. discover_nodes — GET /api/cluster/nodes (membership=available, retry 3x/30s) @@ -59,23 +56,11 @@ } # --------------------------------------------------------------------------- -_NODE_FIELDS_SETS = [ - # newest (9.19+) - ( - "name,model,state,ha,version,serial_number,membership," - "cluster_interfaces,management_interfaces,metrocluster,disaggregated,san_optimized" - ), - # 9.18 without disaggregated - ( - "name,model,state,ha,version,serial_number,membership," - "cluster_interfaces,management_interfaces,metrocluster,san_optimized" - ), - # 9.14 and older — minimal safe set - ( - "name,model,state,ha,version,serial_number,membership," - "cluster_interfaces,management_interfaces,metrocluster" - ), -] +# ONTAP 9 unified — node discovery fields +_NODE_FIELDS = ( + "name,model,state,ha,version,serial_number,membership," + "cluster_interfaces,management_interfaces,metrocluster" +) def _env(key: str, required: bool = True) -> str: @@ -96,20 +81,8 @@ def _env(key: str, required: bool = True) -> str: def _get_nodes(client: OntapClient, **kwargs) -> dict: - """GET /cluster/nodes, trying progressively reduced field sets for older ONTAP versions.""" - last_exc: Exception | None = None - for fields in _NODE_FIELDS_SETS: - try: - return client.get("/cluster/nodes", fields=fields, **kwargs) - except Exception as exc: - if "262197" in str(exc): - logger.warning( - "discover: field unsupported on this version, retrying with reduced fields" - ) - last_exc = exc - continue - raise - raise last_exc # type: ignore[misc] + """GET /cluster/nodes with the standard ONTAP 9 unified field set.""" + return client.get("/cluster/nodes", fields=_NODE_FIELDS, **kwargs) def discover_nodes(client: OntapClient, attempts: int = 3, delay: int = 30) -> dict: