From 48999e146cdeb5a19af5c80f9d8ab72c2be1b15b Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 19:51:09 -0500 Subject: [PATCH 01/16] Added scripts that will add and remove members from a BlueXP organization. --- .../Workload-Factory-API-Samples/README.md | 22 ++- .../bluexp_organization_member_add | 108 ++++++++++++ .../bluexp_organization_member_delete | 105 ++++++++++++ .../get_latency_configuration | 161 ++++++++++++++++++ .../list_bluexp_organization_members | 130 ++++++++++++++ .../list_bluexp_roles | 106 ++++++++++++ 6 files changed, 628 insertions(+), 4 deletions(-) create mode 100755 Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add create mode 100755 Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete create mode 100755 Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration create mode 100755 Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members create mode 100755 Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles diff --git a/Management-Utilities/Workload-Factory-API-Samples/README.md b/Management-Utilities/Workload-Factory-API-Samples/README.md index eb0e56d..32dbdc0 100644 --- a/Management-Utilities/Workload-Factory-API-Samples/README.md +++ b/Management-Utilities/Workload-Factory-API-Samples/README.md @@ -43,26 +43,40 @@ If you do create a new script, please consider contributing it back to this repo | [bluexp_organization_add](bluexp_organization_add) | This adds a new BlueXP organization (a.k.a. account). | | [bluexp_organization_delete](bluexp_organization_delete) | This deletes a BlueXP organization (a.k.a. account). | | [bluexp_organization_rename](bluexp_organization_rename) | This renames a BlueXP organization (a.k.a. account). | -| [credentials_delete](credentials_delete) | This deletes a Workload Factory credential. | +| [bluexp_organization_member_add](bluexp_organization_member_add) | This adds a member to a BlueXP organization (a.k.a. account). | +| [bluexp_organization_member_delete](bluexp_organization_member_delete) | This deletes a member from a BlueXP organization (a.k.a. account). | +| [cicd_clones_create](cicd_clones_create) | This creates a clone in the specified CI/CD project. | +| [cicd_clones_delete](cicd_clones_delete) | This deletes a clone in the specified CI/CD project. | +| [cicd_project_create](cicd_project_create) | This creates a new CI/CD project. | +| [cicd_project_delete](cicd_project_delete) | This deletes a CI/CD project. | | [credentials_create](credentials_create) | This create a Workload Factory credential. | +| [credentials_delete](credentials_delete) | This deletes a Workload Factory credential. | +| [eda_project_config_create](eda_project_config_create) | This creates a new EDA project configuration. | +| [eda_project_config_delete](eda_project_config_delete) | This deletes a EDA project configuration. | | [fsxn_credentials_set](fsxn_credentials_set) | This sets the credentials for a specified FSx for ONTAP file system. | -| [get_latency_details](get_latency_details) | Get the specific details of a latency event. | +| [get_latency_configuration](get_latency_configuration) | Get the latency configuration for the BlueXP account. | +| [get_latency_metrics](get_latency_metrics) | Get the specific details of a latency event. | | [link_associate](link_associate) | This associates a Workload Factory Link with a specified FSx for ONTAP file system. | | [link_delete](link_delete) | This deletes a Workload Factory Link. | | [link_disassociate](link_disassociate) | This disassociates a Workload Factory Link with a specified FSx for ONTAP file system. | | [link_register](link_register) | This registers a Lambda function as a Workload Factory Link. | | [list_bluexp_accts](list_bluexp_accts) | This list all the BlueXP accounts (a.k.a. organizations) that you have access to. | | [list_bluexp_connectors](list_bluexp_connectors) | This list all the BlueXP connectors that you have access to. | -| [list_bluexp_members](list_bluexp_members) | This list all members of a provided BlueXP account. | +| [list_bluexp_organization_members](list_organization_members) | This list all members of a provided BlueXP organization. | +| [list_bluexp_roles](list_bluexp_roles) | This list all the BlueXP roles that you have access to. | | [list_bluexp_workspaces](list_bluexp_workspaces) | This list all the BlueXP workspaces that you have access to. | +| [list_cicd_clones](list_cicd_clones) | This lists all the clones that you have access to in the specified CI/CD project. | +| [list_cicd_projects](list_cicd_projects) | This lists all the CI/CD projects that you have access to. | | [list_credentials](list_credentials) | This lists all the Workload Factory credentials that you have access to. | +| [list_eda_project_config](list_eda_project_config) | This lists the EDA project configuration. | | [list_filesystems](list_filesystems) | This lists all the FSx for ONTAP file systems that you have access to in the specified AWS region. | | [list_latency_events](list_latency_events) | This lists all the latnecy events associated with the BlueXP account. | | [list_links](list_links) | This lists all the Workload Factory Links that you have access to. | | [list_snapmirrors](list_snapmirrors) | This lists all the SnapMirror relationships that are associated with the specified file system. | +| [list_snapshots](list_snapshots) | This lists all the snapshots that are associated with the specified volumes. | | [list_svms](list_svms) | This lists all the SVMs that are associated with the specified file system. | -| [list_volumes](list_volumes) | This lists all the volumes that are associated with the specified file system. | | [list_volume_cifs_shares](list_volume_cifs_shares) | This lists cifs shares associated with a volume in the specified file system. | +| [list_volumes](list_volumes) | This lists all the volumes that are associated with the specified file system. | | [show_fsxn_credentials](show_fsxn_credentials) | This shows the credentials that Workload Factory has stored for the specified FSx for ONTAP file system. | | [snapmirror_break](snapmirror_break) | This breaks the SnapMirror relationship for the specified relationship. | | [snapmirror_create](snapmirror_create) | This creates a SnapMirror relationship between the specified source volume and destination SVM. | diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add new file mode 100755 index 0000000..a68bcc6 --- /dev/null +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add @@ -0,0 +1,108 @@ +#!/bin/bash +# +# This script adds a user to an BlueXP organization. +# +################################################################################ +# Display usage information then exits the script. +################################################################################ +usage () { + cat 1>&2 < + ORGANIZATION_ID= +EOF + exit 1 +} + +################################################################################ +# Main logic starts here. +################################################################################ +tmpout=$(mktemp /tmp/add_organization_to_bluexp-out.XXXXXX) +tmperr=$(mktemp /tmp/add_organization_to_bluexp-err.XXXXXX) +trap 'rm -f $tmpout $tmperr' exit +# +# Source the wf_utils file. +wf_utils=$(command -v wf_utils) +if [ -z "$wf_utils" ]; then + if [ ! -x "./wf_utils" ]; then + cat >&2 <&2 + usage +fi +# +# Check that the required commands are available. +for cmd in jq curl; do + if ! command -v $cmd &> /dev/null; then + echo "Error: The required command '$cmd' was not found. Please install it." >&2 + exit 1 + fi +done +# +# Get an access token. +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token=$BEARER_TOKEN +fi + +if [ -z "$token" ]; then + echo "Failed to get a token." + exit 1 +fi +# +# This uses the same API that the console uses. +run_curl "POST" "$token" "https://$ENDPOINT/v1/management/organizations/$ORGANIZATION_ID/roles/$ROLE_ID/users" $tmpout $tmperr '{"type":"application/vnd.netapp.bxp.userbulk","users":[{"emailId": "'$emailAddress'"}], "version":"1.0"}' 'application/json' +echo "Done." diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete new file mode 100755 index 0000000..4a68a5d --- /dev/null +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete @@ -0,0 +1,105 @@ +#!/bin/bash +# +# This script deletes a user from an BlueXP organization (a.k.a BlueXP account). +# +################################################################################ +# Display usage information then exits the script. +################################################################################ +usage () { + cat 1>&2 < + ORGANIZATION_ID= +EOF + exit 1 +} + +################################################################################ +# Main logic starts here. +################################################################################ +tmpout=$(mktemp /tmp/add_organization_to_bluexp-out.XXXXXX) +tmperr=$(mktemp /tmp/add_organization_to_bluexp-err.XXXXXX) +trap 'rm -f $tmpout $tmperr' exit +# +# Source the wf_utils file. +wf_utils=$(command -v wf_utils) +if [ -z "$wf_utils" ]; then + if [ ! -x "./wf_utils" ]; then + cat >&2 <&2 + usage +fi +# +# Check that the required commands are available. +for cmd in jq curl; do + if ! command -v $cmd &> /dev/null; then + echo "Error: The required command '$cmd' was not found. Please install it." >&2 + exit 1 + fi +done +# +# Get an access token. +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token=$BEARER_TOKEN +fi + +if [ -z "$token" ]; then + echo "Failed to get a token." + exit 1 +fi +# +# This uses the same API that the console uses. +run_curl "DELETE" "$token" "https://$ENDPOINT/v1/management/organizations/$ORGANIZATION_ID/users/$USER_ID" $tmpout $tmperr +echo "Done." diff --git a/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration new file mode 100755 index 0000000..253cf47 --- /dev/null +++ b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration @@ -0,0 +1,161 @@ +#!/bin/bash +# +################################################################################ +# This script is used to get the base EDA -> Latency configuration. +# +# It is dependent on the 'wf_utils' file that is included in this repo. That +# file contains the 'get_token' function that is used to obtain a valid +# access token that is needed to run the Workload Factory APIs. The file needs +# to either be in the command search path or in the current directory. +################################################################################ + +################################################################################ +# This function just prints the usage of this script and exits the program. +################################################################################ +usage() { + cat >&2 < Latency configuration. It will not +report on the latency configuration for individual volumes. To see them use +the -j option. + +Usage: $(basename $0) -t refresh_token -a blueXP_account_ID [-j] [-n] + +Where: refresh_token - Is a refresh token used to obtain an access token needed + to run the Workload Factory APIs. You can obtain a refresh + token by going to https://services.cloud.netapp.com/refresh-token + blueXP_account_ID - is the BlueXP account ID. Run 'list_bluexp_accts' to get a + list of accounts you have access to. + -j - If specified, the output will be in JSON format instead of a table. + -n - If specified, the volume name will be included. Note, this will take extra time to run. + +Instead of passing parameters on the command line, you can set the +following environment variables: + + export REFRESH_TOKEN= + export BLUEXP_ACCOUNT_ID= +EOF + exit 1 +} + +################################################################################ +# Main logic starts here. +################################################################################ +tmpout=$(mktemp /tmp/list_volumes-out.XXXXXX) +tmpout2=$(mktemp /tmp/list_volumes-out2.XXXXXX) +tmpout3=$(mktemp /tmp/list_volumes-out3.XXXXXX) +tmperr=$(mktemp /tmp/list_volumes-err.XXXXXX) +trap 'rm -f $tmpout $tmpout2 $tmpout3 $tmperr' exit +# +# Source the wf_utils file. +wf_utils=$(command -v wf_utils) +if [ -z "$wf_utils" ]; then + if [ ! -x "./wf_utils" ]; then + cat >&2 < /dev/null; then + echo "Error: The required command '$cmd' not found. Please install it." >&2 + exit 1 + fi +done + +token=$(get_token) +if [ -z "$token" ]; then + echo "Error: Failed to obtain an access token. Exiting." >&2 + exit 1 +fi + +URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/alarm/configure" +run_curl GET "$token" "$URL" $tmpout $tmperr +nextToken=$(jq -r '.nextToken' $tmpout) +while [ "$nextToken" != "null" -a ! -z "$nextToken" ]; do + run_curl GET "$token" "${URL}?nextToken=$nextToken" $tmpout2 $tmperr + cat $tmpout2 >> $tmpout + nextToken=$(jq -r '.nextToken' $tmpout2) +done + +if [ "$JSON_OUTPUT" == true ]; then + cat $tmpout $tmperr +else + # + # First get the default latency configuration. + jq_query='"\(.accountId),\(.createdBy),\(.status),\(.config.warning.readLatency.latencyThresholdMs),\(.config.warning.writeLatency.latencyThresholdMs),\(.config.critical.readLatency.latencyThresholdMs),\(.config.critical.writeLatency.latencyThresholdMs)"' + if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then + : + else + echo "Error: Failed to parse the output from the API." >&2 + cat $tmperr >&2 + exit 1 + fi + echo "Default Latency Configuration:" + sort -f $tmpout2 | column -s, -t -R 4,5,6,7 -N "Account ID,Created By,Status,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" + # + # Now get the latency configuration for each volume. + jq_query='.config.filesystems | to_entries | .[] | .key + " " + (.value | to_entries | .[] | .key + " " + (.value.warning.readLatency.latencyThresholdMs | tostring) + " " + (.value.warning.writeLatency.latencyThresholdMs | tostring) + " " + (.value.critical.readLatency.latencyThresholdMs | tostring) + " " + (.value.critical.writeLatency.latencyThresholdMs | tostring) + " ")' + if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then + : + else + echo "Error: Failed to parse the output from the API." >&2 + cat $tmperr >&2 + exit 1 + fi + if [ "$INCLUDE_NAMES" == false ]; then + printf "\nVolume specific Latency Configuration:\n" + sort -f $tmpout2 | column -s' ' -t -R 3,4,5,6 -N "File System ID,Volume ID,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" + else + # + # Get all the unique file systems: + awk '{print $1}' $tmpout2 | sort -u > $tmpout + # + # Create a lookup table of file system to volume mapping. + > $tmpout3 + for fs in $(cat $tmpout); do + list_volumes -f $fs >> $tmpout3 + done + + > $tmpout + while read fsId volId wrt wwr crt cwt; do + volumeName=$(grep $volId $tmpout3 | awk '{print $1}') + if [ -z "$volumeName" ]; then + volumeName="N/A" + fi + echo "$fsId,$volId,$volumeName,$wrt,$wwr,$crt,$cwt," >> $tmpout + done < $tmpout2 + printf "\nVolume specific Latency Configuration:\n" + sort -f $tmpout | column -s, -t -R 4,5,6,7 -N "File System ID,Volume ID,Volume Name,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" + fi +fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members new file mode 100755 index 0000000..8005d38 --- /dev/null +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members @@ -0,0 +1,130 @@ +#!/bin/bash +# +# This script is used to list all the members assigned to a BlueXP organization. +# +# It is dependent on the 'wf_utils' file that is included in this repo. That +# file contains the 'get_token' function that is used to obtain a valid +# access token that is needed to run the Workload Factory APIs. The file needs +# to either be in the command search path or in the current directory. +################################################################################ + +################################################################################ +# Display usage information then exits the script. +################################################################################ +usage () { + cat >&2 < + export ORGANIZATION_ID= +EOF + exit 1 +} + +tmpout=/tmp/list_bluexp_user-out.$$ +tmpout2=/tmp/list_bluexp_user-out2.$$ +tmperr=/tmp/list_bluexp_user-err.$$ +trap 'rm -f $tmpout $tmperr' exit +# +# Source the wf_utils file. +wf_utils=$(command -v wf_utils) +if [ -z "$wf_utils" ]; then + if [ ! -x "./wf_utils" ]; then + cat <&2 +Error: The 'wf_utils' script was not found in the current directory or in the command search path. +It is required to run this script. You can download it from: +https://github.com/NetApp/FSx-ONTAP-samples-scripts/tree/main/Management-Utilities/Workload-Factory-API-Samples +EOF + exit 1 + else + wf_utils=./wf_utils + fi +fi +. "$wf_utils" +# +# Parse the command line options. +raw=false +: "${ENDPOINT:=api.bluexp.netapp.com}" +while getopts ht:je:b:o: opt; do + case $opt in + t) REFRESH_TOKEN="$OPTARG" ;; + o) ORGANIZATION_ID="$OPTARG" ;; + b) BEARER_TOKEN="$OPTARG" ;; + j) raw=true ;; + e) ENDPOINT="$OPTARG" ;; + *) usage ;; + esac +done +# +# Declare an array of required options and the error message to display if they are not set. +declare -A required_options +required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh tokon is required to run this script. It can be obtain from this web page: + https://services.cloud.netapp.com/refresh-token\n\n' +required_options["ORGANIZATION_ID"]='Error: The ID of the organization is required to run this script. +You can list a list of account by running the list_bluexp_accts script found in this repo.\n\n' + +check_required_options +# +# Check that the required commands are available. +for cmd in jq curl; do + if ! command -v $cmd &> /dev/null; then + echo "Error: The required command '$cmd' was not found. Please install it." >&2 + exit 1 + fi +done + +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token="$BEARER_TOKEN" +fi + +if [ -z "$token" ]; then + echo "Error: Failed to obtain an access token. Exiting." >&2 + exit 1 +fi +filter="$(urlencode userType ne \"agent\")" +numPerPage=100 +count=1 # Just give it an initial value to enter the loop +total=0 +while [ $total -lt $count ]; do + run_curl GET "$token" "https://$ENDPOINT/v1/management/organizations/$ORGANIZATION_ID/users?limit=${numPerPage}&skip=${total}&filter=$filter" $tmpout $tmperr '' 'application/vnd.netapp.bxp.users.extended+json' + count="$(jq -r '.count' $tmpout 2> /dev/null)" + if [ "$count" == 0 ]; then + echo "No members found for the specified organization: $ORGANIZATION_ID." >&2 + exit 0 + fi + let total+=numPerPage + + if [ "$raw" == true ]; then + jq -r . $tmpout + else + jq_query='.items[] | "\(.id),\(.name)," + if(.email != null) then .email else "N/A" end + ",\(.userType)"' + if jq -r "$jq_query" $tmpout >> $tmpout2 2> $tmperr; then + : + else + echo "Error: Failed to parse the response from the API." >&2 + cat $tmperr >&2 + exit 1 + fi + fi +done + +if [ "$raw" == true ]; then + exit 0 +fi +sort -f -t, -k 4,4r -k 2,2 $tmpout2 | column -s, -t -N "ID,Name,Email,Type" diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles new file mode 100755 index 0000000..2b33df0 --- /dev/null +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles @@ -0,0 +1,106 @@ +#!/bin/bash +# +# This script is used to list all the BlueXP user roles that a user has +# access to. +# +# It is dependent on the 'wf_utils' file that is included in this repo. That +# file contains the 'get_token' function that is used to obtain a valid +# access token that is needed to run the Workload Factory APIs. The file needs +# to either be in the command search path or in the current directory. +################################################################################ + +################################################################################ +# Display usage information then exits the script. +################################################################################ +usage () { + cat >&2 < +EOF + exit 1 +} + +tmpout=/tmp/list_roles-out.$$ +tmperr=/tmp/list_roles-err.$$ +trap 'rm -f $tmpout $tmperr' exit +# +# Source the wf_utils file. +wf_utils=$(command -v wf_utils) +if [ -z "$wf_utils" ]; then + if [ ! -x "./wf_utils" ]; then + cat <&2 +Error: The 'wf_utils' script was not found in the current directory or in the command search path. +It is required to run this script. You can download it from: +https://github.com/NetApp/FSx-ONTAP-samples-scripts/tree/main/Management-Utilities/Workload-Factory-API-Samples +EOF + exit 1 + else + wf_utils=./wf_utils + fi +fi +. "$wf_utils" +# +# Parse the command line options. +raw=false +: "${ENDPOINT:=api.bluexp.netapp.com}" +while getopts ht:je:b: opt; do + case $opt in + t) REFRESH_TOKEN="$OPTARG" ;; + b) BEARER_TOKEN="$OPTARG" ;; + j) raw=true ;; + e) ENDPOINT="$OPTARG" ;; + *) usage ;; + esac +done +# +# Declare an array of required options and the error message to display if they are not set. +declare -A required_options +required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh tokon is required to run this script. It can be obtain from this web page: + https://services.cloud.netapp.com/refresh-token\n\n' + +check_required_options +# +# Check that the required commands are available. +for cmd in jq curl; do + if ! command -v $cmd &> /dev/null; then + echo "Error: The required command '$cmd' was not found. Please install it." >&2 + exit 1 + fi +done + +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token="$BEARER_TOKEN" +fi + +if [ -z "$token" ]; then + echo "Error: Failed to obtain an access token. Exiting." >&2 + exit 1 +fi +filter="$(urlencode isUserRole eq \"true\" and category eq \"IAM\")" +run_curl GET "$token" "https://$ENDPOINT/v1/management/roles?filter=$filter" $tmpout $tmperr +if [ "$raw" = true ]; then + cat $tmpout + exit 0 +fi + +if jq -r '.items[] | "\(.id),\(.name)"' $tmpout > $tmperr; then + cat $tmperr | column -s, -t -N ID,Name +else + echo "Error: Failed to parse the response from the API." >&2 + exit 1 +fi From 333a2f53f6eae779ae9322f8819e669f6e12831b Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 19:52:33 -0500 Subject: [PATCH 02/16] Fixed a bug where if someone provided a different refresh token and the last generate access token was less than 24 hours old, the script would not generate a new access token. --- .../Workload-Factory-API-Samples/wf_utils | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/wf_utils b/Management-Utilities/Workload-Factory-API-Samples/wf_utils index b477375..01bab2b 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/wf_utils +++ b/Management-Utilities/Workload-Factory-API-Samples/wf_utils @@ -20,10 +20,18 @@ # if it is, it will just return the contents of the file. If it is older than # 24 hours, it will create a new token, output it to standard out and # store it in the file. +# +# It will store the checksum of the refresh token used to create the access +# token in a file named ".blueXP_token_checksum" in the account's home +# directory. If the checksum of the current refresh token is different than +# the one stored in the file, it will create a new token and update the +# checksum file. +# ################################################################################ get_token() { tokenFile="$HOME/.blueXP_token" + tokenCksumFile="$HOME/.blueXP_token_checksum" if [ -z "$REFRESH_TOKEN" ]; then echo "Error: The REFRESH_TOKEN environment variable has not been set." >&2 @@ -31,7 +39,7 @@ get_token() { fi # # Ensure all the required commands are available. - for cmd in curl; do + for cmd in curl cksum; do if ! command -v $cmd &> /dev/null; then echo "Error: $cmd is required but not installed." >&2 exit 1 @@ -44,6 +52,17 @@ get_token() { createToken=false if [ -r $tokenFile ]; then + cksum=$(echo -n "$REFRESH_TOKEN" | cksum | awk '{print $1}') + if [ -r $tokenCksumFile ]; then + old_cksum=$(cat $tokenCksumFile) + if [ "$cksum" != "$old_cksum" ]; then + createToken=true + echo -n "$cksum" > $tokenCksumFile + fi + else + echo "$cksum" > $tokenCksumFile + createToken=true + fi let diff="$(date +%s) - $(date -r $tokenFile +%s)" if [ $diff -gt $token_valid ]; then createToken=true From 08d9400a949511d290a91d662fa8862e8026805b Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 19:55:00 -0500 Subject: [PATCH 03/16] Added an option to provide a different endpoint for the API and provide an access_token instead of creating one from a refresh_token. Also, added support to add a GovCloud account. --- .../bluexp_organization_add | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add index 230c675..adaf1bf 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add @@ -7,12 +7,17 @@ ################################################################################ usage () { cat 1>&2 <&2 + usage +fi # # Check that the required commands are available. for cmd in jq curl; do @@ -71,7 +84,12 @@ for cmd in jq curl; do done # # Get an access token. -token=$(get_token) +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token=$BEARER_TOKEN +fi + if [ -z "$token" ]; then echo "Failed to get a token." exit 1 @@ -79,5 +97,6 @@ fi # # Add the organization to the BlueXP workspace. echo -n "Creating organization ${ORGANIZATION_NAME}..." -run_curl "POST" "$token" "https://api.bluexp.netapp.com/tenancy/account/$ORGANIZATION_NAME" $tmpout $tmperr '{"name": "'$ORGANIZATION_NAME'", "resourceType": "organization", "type": "application/vnd.netapp.bxp.resource", "version": "1.0"}' 'application/json' +#run_curl "POST" "$token" "https://$ENDPOINT/tenancy/account/$ORGANIZATION_NAME" $tmpout $tmperr '{"name": "'$ORGANIZATION_NAME'", "resourceType": "organization", "type": "application/vnd.netapp.bxp.resource", "version": "1.0"}' 'application/json' +run_curl "POST" "$token" "https://$ENDPOINT/tenancy/account/$ORGANIZATION_NAME" $tmpout $tmperr '{"isSaasDisabled": '$GovCloud'}' 'application/json' echo "Done." From cfa16a71a6122604f9a2f0f46499d96733787a08 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 19:59:20 -0500 Subject: [PATCH 04/16] Added an option to provide a different endpoint for the API and provide an access_token instead of creating one from a refresh_token. Also, added support to add a GovCloud account. --- .../bluexp_organization_delete | 37 +++++++++++++++---- .../list_bluexp_accts | 32 +++++++++++++--- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete index 774622b..f67da76 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete @@ -1,18 +1,26 @@ #!/bin/bash # -# This script deletes an organization from a BlueXP account. +# This script deletes an BlueXP organization. # ################################################################################ # Display usage information then exits the script. ################################################################################ usage () { cat 1>&2 <&2 + usage +fi # # Check that the required commands are available. for cmd in jq curl; do @@ -73,12 +89,17 @@ for cmd in jq curl; do done # # Get an access token. -token=$(get_token) +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token="$BEARER_TOKEN" +fi + if [ -z "$token" ]; then echo "Failed to get a token." exit 1 fi # echo -n "Deleting organization ${ORGANIZATION_ID}..." -run_curl "DELETE" "$token" "https://api.bluexp.netapp.com/v1/management/organizations/$ORGANIZATION_ID" $tmpout $tmperr +run_curl "DELETE" "$token" "https://$ENDPOINT/tenancy/account/$ORGANIZATION_ID" $tmpout $tmperr echo "Done." diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts index 246c3f7..79e256a 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts @@ -17,11 +17,14 @@ usage () { This script is used to list all the BlueXP accounts (a.k.a. organizations) that you have access to. -Usage is: $(basename $0) -t refresh_token +Usage is: $(basename $0) -t refresh_token [-j] [-e ENDPOINT] [-b BEARER_TOKEN] Where: refresh_token - Is a refresh token used to obtain an access token needed to run the Workload Factory APIs. You can obtain a refresh token by going to https://services.cloud.netapp.com/refresh-token + j - Output the raw JSON response from the API instead of a formatted table. + ENDPOINT - The BlueXP API endpoint to use. Default is api.bluexp.netapp.com. + BEARER_TOKEN - A valid Bearer token that can be used instead of a refresh token. Note, instead of passing parameters on the command line, you can set the following environment variables instead: @@ -52,9 +55,14 @@ fi . "$wf_utils" # # Parse the command line options. -while getopts ht: opt; do +raw=false +: "${ENDPOINT:=api.bluexp.netapp.com}" +while getopts ht:je:b: opt; do case $opt in t) REFRESH_TOKEN="$OPTARG" ;; + b) BEARER_TOKEN="$OPTARG" ;; + j) raw=true ;; + e) ENDPOINT="$OPTARG" ;; *) usage ;; esac done @@ -74,15 +82,27 @@ for cmd in jq curl; do fi done -token=$(get_token) +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token="$BEARER_TOKEN" +fi + if [ -z "$token" ]; then echo "Error: Failed to obtain an access token. Exiting." >&2 exit 1 fi +# +# /tenancy/account doesn't provide both the ID and UUID. +#run_curl GET "$token" https://$ENDPOINT/tenancy/account $tmpout $tmperr +run_curl GET "$token" https://$ENDPOINT/v1/management/organizations $tmpout $tmperr +if [ "$raw" = true ]; then + cat $tmpout + exit 0 +fi -run_curl GET "$token" https://api.bluexp.netapp.com/v1/management/organizations $tmpout $tmperr -if jq -r '.items[] | "\(.name) \(.legacyId) \(.id)"' $tmpout > $tmperr; then - cat $tmperr | column -t -N Name,ID,UUID +if jq -r '.items[] | "\(.name) \(.legacyId) \(.id) " + ((.tags[] | select (".internal:bxp:gov") | if(."internal:bxp:gov") then ."internal:bxp:gov" else empty end) | tostring) + " " + ((.tags[] | select (".internal:bxp:saas") | if(."internal:bxp:saas") then ."internal:bxp:saas" else empty end) | tostring)' $tmpout > $tmperr; then + cat $tmperr | column -t -N "Name,ID,Organization ID,GovCloud,SaaS Enabled" else echo "Error: Failed to parse the response from the API." >&2 exit 1 From 6918379ec287f3d5749a7c90c74546a873031842 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:00:02 -0500 Subject: [PATCH 05/16] Updated it to use the publised API. --- .../bluexp_organization_rename | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_rename b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_rename index 4d135c0..4b8f216 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_rename +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_rename @@ -7,12 +7,16 @@ ################################################################################ usage () { cat 1>&2 < Date: Tue, 30 Jun 2026 20:01:50 -0500 Subject: [PATCH 06/16] Corrected the logic for the required parameters. --- .../Workload-Factory-API-Samples/fsxn_credentials_set | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/fsxn_credentials_set b/Management-Utilities/Workload-Factory-API-Samples/fsxn_credentials_set index 03a1db0..f987bf7 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/fsxn_credentials_set +++ b/Management-Utilities/Workload-Factory-API-Samples/fsxn_credentials_set @@ -104,12 +104,15 @@ required_options["FILESYSTEM_ID"]='Error: The ID of the FSxN file system is requ check_required_options -if [ -n "$USER_ID" -a -n "$PASSWORD" -a -n "$SECRET_ARN" ]; then +if [ -n "$USER_ID" -a -n "$SECRET_ARN" ]; then echo "Error: You can only provide either user_id and password OR secret_arn at the same time." >&2 usage -elif [ -z "$USER_ID" -a -z "$PASSWORD" -a -z "$SECRET_ARN" ]; then +elif [ -z "$USER_ID" -a -z "$SECRET_ARN" ]; then echo "Error: You must provide either user_id and password OR secret_arn." >&2 usage +elif [ -n "$USER_ID" -a -z "$PASSWORD" ]; then + echo "Error: You must provide a password when providing a user_id." >&2 + usage fi # # Check that the required commands are available. From 45b26cdcccbbe39102aa0d3ba88975fd087bd3a1 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:02:42 -0500 Subject: [PATCH 07/16] Added an option to provide a different endpoint for the API and provide an access_token instead of creating one from a refresh_token. --- .../eda_project_config_delete | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/eda_project_config_delete b/Management-Utilities/Workload-Factory-API-Samples/eda_project_config_delete index 91613fb..ae82d7f 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/eda_project_config_delete +++ b/Management-Utilities/Workload-Factory-API-Samples/eda_project_config_delete @@ -16,13 +16,16 @@ usage() { cat >&2 <&2 + exit 1 +fi # # Check that the required commands are available. for cmd in jq curl; do @@ -83,12 +92,16 @@ for cmd in jq curl; do fi done -token=$(get_token) +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token=$BEARER_TOKEN +fi if [ -z "$token" ]; then echo "Error: Failed to obtain an access token. Exiting." >&2 exit 1 fi -URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/configure" +URL="https://$ENDPOINT/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/configure" run_curl DELETE "$token" "$URL" $tmpout $tmperr echo "EDA Project filter deleted successfully." From 8b3625b08f7963a48ca8383fe2191766c5f61a5d Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:04:47 -0500 Subject: [PATCH 08/16] Updated the calculation of days left. --- .../Workload-Factory-API-Samples/list_cicd_clones | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones index 0c39eaa..c4e0fcd 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones +++ b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones @@ -103,7 +103,7 @@ fi if [ "$raw" = true ]; then jq_query='.' else - jq_query='.items[] | "\(.name) \(.size/1024/1024/1024) \(.daysLeft) \(.volumeUuid) \(.status)"' + jq_query='.items[] | .name + " " + if(.size) then .size/1024/1024/1024 | tostring else "N/A" end + " " + if(.daysLeft) then .daysLeft | tostring else "N/A" end + " " + .volumeUuid + " " + .status' fi URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/projects/${PROJECT_ID}/operations/clones" From b959df1c6375dc03d739a1d8de191d09786d5a66 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:05:46 -0500 Subject: [PATCH 09/16] Added an option to provide a different endpoint for the API and provide an access_token instead of creating one from a refresh_token. --- .../list_cicd_projects | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects index 4f70137..31f93d7 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects +++ b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects @@ -16,14 +16,17 @@ usage() { cat >&2 <&2 + exit 1 +fi # # Check that the required commands are available. for cmd in jq curl; do @@ -87,7 +96,11 @@ for cmd in jq curl; do fi done -token=$(get_token) +if [ -z "$BEARER_TOKEN" ]; then + token=$(get_token) +else + token=$BEARER_TOKEN +fi if [ -z "$token" ]; then echo "Error: Failed to obtain an access token. Exiting." >&2 exit 1 @@ -99,7 +112,7 @@ else jq_query='.items[] | "\(.projectName) \(.projectId) \(.fileSystemId) \(.volumeName) \(.volumeId) \(.numberOfClones)"' fi -URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/projects" +URL="https://$ENDPOINT/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/projects" run_curl GET "$token" "$URL" $tmpout $tmperr if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then : From c385b865721fb6fa6dac52291a224b6fbe442d01 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:06:49 -0500 Subject: [PATCH 10/16] Fixed a typo in the usage statement. --- .../Workload-Factory-API-Samples/cicd_clones_delete | 4 ++-- .../Workload-Factory-API-Samples/cicd_project_create | 4 ++-- .../Workload-Factory-API-Samples/cicd_project_delete | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/cicd_clones_delete b/Management-Utilities/Workload-Factory-API-Samples/cicd_clones_delete index a9cd9d6..e19dacc 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/cicd_clones_delete +++ b/Management-Utilities/Workload-Factory-API-Samples/cicd_clones_delete @@ -1,7 +1,7 @@ #!/bin/bash # ################################################################################ -# This script is used to delete a EDA CI/CD clone. +# This script is used to delete an EDA CI/CD clone. # # It is dependent on the 'wf_utils' file that is included in this repo. That # file contains the 'get_token' function that is used to obtain a valid @@ -14,7 +14,7 @@ ################################################################################ usage() { cat >&2 <&2 <&2 < Date: Tue, 30 Jun 2026 20:07:47 -0500 Subject: [PATCH 11/16] Effectively renamed to list_bluexp_organization_members --- .../list_bluexp_members | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100755 Management-Utilities/Workload-Factory-API-Samples/list_bluexp_members diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_members b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_members deleted file mode 100755 index b697017..0000000 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_members +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -# -# This script is used to list all the BlueXP members that are -# associated with the BlueXP account. -# -# It is dependent on the 'wf_utils' file that is included in this repo. That -# file contains the 'get_token' function that is used to obtain a valid -# access token that is needed to run the Workload Factory APIs. The file needs -# to either be in the command search path or in the current directory. -################################################################################ - -################################################################################ -# Display usage information then exits the script. -################################################################################ -usage () { - cat >&2 < - export BLUEXP_ACCOUNT_UUID= -EOF - exit 1 -} - -tmpout=/tmp/list_bluexp_members-out.$$ -tmpout2=/tmp/list_bluexp_members-out2.$$ -tmperr=/tmp/list_bluexp_members-err.$$ -trap 'rm -f $tmpout $tmpout2 $tmperr' exit -# -# Source the wf_utils file. -wf_utils=$(command -v wf_utils) -if [ -z "$wf_utils" ]; then - if [ ! -x "./wf_utils" ]; then - cat <&2 -Error: The 'wf_utils' script was not found in the current directory or in the command search path. -It is required to run this script. You can download it from: -https://github.com/NetApp/FSx-ONTAP-samples-scripts/tree/main/Management-Utilities/Workload-Factory-API-Samples -EOF - exit 1 - else - wf_utils=./wf_utils - fi -fi -. "$wf_utils" -# -# Parse the command line options. -while getopts ht:u: opt; do - case $opt in - t) REFRESH_TOKEN="$OPTARG" ;; - u) BLUEXP_ACCOUNT_UUID="$OPTARG" ;; - *) usage ;; - esac -done -# -# Declare an array of required options and the error message to display if they are not set. -declare -A required_options -required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh tokon is required to run this script. It can be obtain from this web page: - https://services.cloud.netapp.com/refresh-token\n\n' -required_options["BLUEXP_ACCOUNT_UUID"]='Error: A BlueXP account UUID is required to run this script. -You can see the list of accounts you have access to by running the "list_bluexp_accts" script -found in this GitHub repository: https://github.com/NetApp/FSx-ONTAP-samples-scripts/tree/main/Management-Utilities/Workload-Factory-API-Samples\n\n' - -check_required_options -# -# Check that the required commands are available. -for cmd in jq curl; do - if ! command -v $cmd &> /dev/null; then - echo "Error: The required command '$cmd' was not found. Please install it." >&2 - exit 1 - fi -done - -token=$(get_token) -if [ -z "$token" ]; then - echo "Error: Failed to obtain an access token. Exiting." >&2 - exit 1 -fi -filter=$(urlencode "userType ne 'agent'") -numPerPage=1000 -run_curl GET "$token" "https://api.bluexp.netapp.com/v1/management/organizations/$BLUEXP_ACCOUNT_UUID/users?limit=${numPerPage}&filter=$filter" $tmpout $tmperr "" "application/vnd.netapp.bxp.users.extended+json" -count=$(jq -r '.count' $tmpout 2> /dev/null) -if [[ "$count" == 0 ]]; then - echo "No members found for the specified BlueXP account UUID: $BLUEXP_ACCOUNT_UUID." >&2 - echo "Did you provide the UUID or the iD? This script needs the UUID." >&2 - exit 0 -fi -total=$numPerPage - -jq_query='.items[] | .name + "," + if(.userType == "user") then .userType + "," + .id else .userType + "," + .auth0Id end + "," + .email' -if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then - : -else - echo "Error: Failed to parse the response from the API." >&2 - cat $tmperr >&2 - exit 1 -fi -# -# Check to see if there are more. -while [ "$total" -lt "$count" ]; do - run_curl GET "$token" "https://api.bluexp.netapp.com/v1/management/organizations/$BLUEXP_ACCOUNT_UUID/users?limit=${numPerPage}&skip=${total}&filter=$filter" $tmpout $tmperr "" "application/vnd.netapp.bxp.users.extended+json" - if jq -r "$jq_query" $tmpout >> $tmpout2 2> $tmperr; then - : - else - echo "Failed to parse the output from the API." - cat $tmperr >&2 - exit 1 - fi - let total+=numPerPage -done -sort -f $tmpout2 | column -s, -t -N Name,Type,ID,Email From d131658fe54a9bfa494fa93ebc1f79d76ffc6f8d Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:08:25 -0500 Subject: [PATCH 12/16] Added an option to provide a different endpoint for the API and provide an access_token instead of creating one from a refresh_token. --- .../list_eda_project_config | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_eda_project_config b/Management-Utilities/Workload-Factory-API-Samples/list_eda_project_config index 8bdd177..cbd48b3 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_eda_project_config +++ b/Management-Utilities/Workload-Factory-API-Samples/list_eda_project_config @@ -16,13 +16,15 @@ usage() { cat >&2 <&2 exit 1 @@ -99,7 +111,7 @@ else jq_query='.createdBy as $cb | (.items[] | select(.target == "tag") | "\(.label) \(.key) " + $cb)' fi -URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/configure" +URL="https://$ENDPOINT/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/configure" run_curl GET "$token" "$URL" $tmpout $tmperr if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then : From a1d77679d7650392fc69771b1b6d08724e0638d9 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Tue, 30 Jun 2026 20:09:03 -0500 Subject: [PATCH 13/16] Added a message if no latency events were found. --- .../Workload-Factory-API-Samples/list_latency_events | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_latency_events b/Management-Utilities/Workload-Factory-API-Samples/list_latency_events index df463fb..ebcdef5 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_latency_events +++ b/Management-Utilities/Workload-Factory-API-Samples/list_latency_events @@ -127,5 +127,10 @@ done if [ "$JSON_OUTPUT" == true ]; then cat $tmpout2 else + count=$(jq -r '.count' $tmpout) + if [ "$count" == "0" ]; then + echo "No latency events were found." + exit 0 + fi sort -f $tmpout2 | column -s, -t -N "Timestamnp,File System Name,Volume Name,Volume ID,Region,Alert Type,Event Type,Latency,Median Latency,IOPS,Median Over Percent" fi From 77365651e4d2f74d29fe2e13993455ece168e630 Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Wed, 1 Jul 2026 13:23:35 -0500 Subject: [PATCH 14/16] Made it where the -j option would at least 'pretty print' the json output. --- .../get_latency_configuration | 94 ++++++++++--------- .../list_bluexp_accts | 6 +- .../list_bluexp_organization_members | 7 +- .../list_bluexp_roles | 8 +- .../list_cicd_clones | 3 +- .../list_cicd_projects | 3 +- 6 files changed, 70 insertions(+), 51 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration index 253cf47..b146c47 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration +++ b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration @@ -109,53 +109,57 @@ while [ "$nextToken" != "null" -a ! -z "$nextToken" ]; do done if [ "$JSON_OUTPUT" == true ]; then - cat $tmpout $tmperr -else - # - # First get the default latency configuration. - jq_query='"\(.accountId),\(.createdBy),\(.status),\(.config.warning.readLatency.latencyThresholdMs),\(.config.warning.writeLatency.latencyThresholdMs),\(.config.critical.readLatency.latencyThresholdMs),\(.config.critical.writeLatency.latencyThresholdMs)"' - if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then + if jq -r . $tmpout; then : else - echo "Error: Failed to parse the output from the API." >&2 - cat $tmperr >&2 - exit 1 + cat $tmpout $tmperr fi - echo "Default Latency Configuration:" - sort -f $tmpout2 | column -s, -t -R 4,5,6,7 -N "Account ID,Created By,Status,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" + exit +fi +# +# First get the default latency configuration. +jq_query='"\(.accountId),\(.createdBy),\(.status),\(.config.warning.readLatency.latencyThresholdMs),\(.config.warning.writeLatency.latencyThresholdMs),\(.config.critical.readLatency.latencyThresholdMs),\(.config.critical.writeLatency.latencyThresholdMs)"' +if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then + : +else + echo "Error: Failed to parse the output from the API." >&2 + cat $tmperr >&2 + exit 1 +fi +echo "Default Latency Configuration:" +sort -f $tmpout2 | column -s, -t -R 4,5,6,7 -N "Account ID,Created By,Status,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" +# +# Now get the latency configuration for each volume. +jq_query='.config.filesystems | to_entries | .[] | .key + " " + (.value | to_entries | .[] | .key + " " + (.value.warning.readLatency.latencyThresholdMs | tostring) + " " + (.value.warning.writeLatency.latencyThresholdMs | tostring) + " " + (.value.critical.readLatency.latencyThresholdMs | tostring) + " " + (.value.critical.writeLatency.latencyThresholdMs | tostring) + " ")' +if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then + : +else + echo "Error: Failed to parse the output from the API." >&2 + cat $tmperr >&2 + exit 1 +fi +if [ "$INCLUDE_NAMES" == false ]; then + printf "\nVolume specific Latency Configuration:\n" + sort -f $tmpout2 | column -s' ' -t -R 3,4,5,6 -N "File System ID,Volume ID,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" +else # - # Now get the latency configuration for each volume. - jq_query='.config.filesystems | to_entries | .[] | .key + " " + (.value | to_entries | .[] | .key + " " + (.value.warning.readLatency.latencyThresholdMs | tostring) + " " + (.value.warning.writeLatency.latencyThresholdMs | tostring) + " " + (.value.critical.readLatency.latencyThresholdMs | tostring) + " " + (.value.critical.writeLatency.latencyThresholdMs | tostring) + " ")' - if jq -r "$jq_query" $tmpout > $tmpout2 2> $tmperr; then - : - else - echo "Error: Failed to parse the output from the API." >&2 - cat $tmperr >&2 - exit 1 - fi - if [ "$INCLUDE_NAMES" == false ]; then - printf "\nVolume specific Latency Configuration:\n" - sort -f $tmpout2 | column -s' ' -t -R 3,4,5,6 -N "File System ID,Volume ID,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" - else - # - # Get all the unique file systems: - awk '{print $1}' $tmpout2 | sort -u > $tmpout - # - # Create a lookup table of file system to volume mapping. - > $tmpout3 - for fs in $(cat $tmpout); do - list_volumes -f $fs >> $tmpout3 - done - - > $tmpout - while read fsId volId wrt wwr crt cwt; do - volumeName=$(grep $volId $tmpout3 | awk '{print $1}') - if [ -z "$volumeName" ]; then - volumeName="N/A" - fi - echo "$fsId,$volId,$volumeName,$wrt,$wwr,$crt,$cwt," >> $tmpout - done < $tmpout2 - printf "\nVolume specific Latency Configuration:\n" - sort -f $tmpout | column -s, -t -R 4,5,6,7 -N "File System ID,Volume ID,Volume Name,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" - fi + # Get all the unique file systems: + awk '{print $1}' $tmpout2 | sort -u > $tmpout + # + # Create a lookup table of file system to volume mapping. + > $tmpout3 + for fs in $(cat $tmpout); do + list_volumes -f $fs >> $tmpout3 + done + + > $tmpout + while read fsId volId wrt wwr crt cwt; do + volumeName=$(grep $volId $tmpout3 | awk '{print $1}') + if [ -z "$volumeName" ]; then + volumeName="N/A" + fi + echo "$fsId,$volId,$volumeName,$wrt,$wwr,$crt,$cwt," >> $tmpout + done < $tmpout2 + printf "\nVolume specific Latency Configuration:\n" + sort -f $tmpout | column -s, -t -R 4,5,6,7 -N "File System ID,Volume ID,Volume Name,Warning Read Threshold,Warning Write Threshold,Critical Read Threshold,Critical Write Threshold" fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts index 79e256a..9fdf0ef 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts @@ -97,7 +97,11 @@ fi #run_curl GET "$token" https://$ENDPOINT/tenancy/account $tmpout $tmperr run_curl GET "$token" https://$ENDPOINT/v1/management/organizations $tmpout $tmperr if [ "$raw" = true ]; then - cat $tmpout + if jq -r . $tmpout; then + : + else + cat $tmpout $tmperr + fi exit 0 fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members index 8005d38..634b068 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members @@ -111,7 +111,12 @@ while [ $total -lt $count ]; do let total+=numPerPage if [ "$raw" == true ]; then - jq -r . $tmpout + if jq -r . $tmpout; then + : + else + cat $tmpout $tmperr + exit + fi else jq_query='.items[] | "\(.id),\(.name)," + if(.email != null) then .email else "N/A" end + ",\(.userType)"' if jq -r "$jq_query" $tmpout >> $tmpout2 2> $tmperr; then diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles index 2b33df0..f816c02 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles @@ -93,8 +93,12 @@ if [ -z "$token" ]; then fi filter="$(urlencode isUserRole eq \"true\" and category eq \"IAM\")" run_curl GET "$token" "https://$ENDPOINT/v1/management/roles?filter=$filter" $tmpout $tmperr -if [ "$raw" = true ]; then - cat $tmpout +if [ "$raw" == true ]; then + if jq -r . $tmpout; then + : + else + cat $tmpout $tmperr + fi exit 0 fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones index c4e0fcd..845f29d 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones +++ b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_clones @@ -129,7 +129,8 @@ while [ "$nextToken" != "null" ]; do fi nextToken=$(jq -r '.nextToken' $tmpout) done -if [ "$raw" = true ]; then + +if [ "$raw" == true ]; then cat $tmpout2 else sort -f $tmpout2 | column -t -N "Name,Size (GB),Days Left,Volume UUID,Status" diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects index 31f93d7..96e0e87 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects +++ b/Management-Utilities/Workload-Factory-API-Samples/list_cicd_projects @@ -135,7 +135,8 @@ while [ "$nextToken" != "null" ]; do fi nextToken=$(jq -r '.nextToken' $tmpout) done -if [ "$raw" = true ]; then + +if [ "$raw" == true ]; then cat $tmpout2 else sort -f $tmpout2 | column -t -N "Name,Project ID,File System,Volume Name,Volume ID,Number of Clones" From 6bba284d8e93466e7f5e4f6b84de94c04032ebeb Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Wed, 1 Jul 2026 17:33:27 -0500 Subject: [PATCH 15/16] Added new scripts. --- .../Workload-Factory-API-Samples/README.md | 2 +- .../bluexp_organization_add | 2 +- .../bluexp_organization_delete | 2 +- .../bluexp_organization_member_add | 2 +- .../bluexp_organization_member_delete | 10 +++++----- .../bluexp_organization_rename | 6 +++--- .../fsxn_credentials_set | 10 +++++----- .../get_latency_configuration | 12 ++++-------- .../Workload-Factory-API-Samples/list_bluexp_accts | 10 ++++------ .../list_bluexp_organization_members | 11 +++++++---- .../Workload-Factory-API-Samples/list_bluexp_roles | 11 +++++------ .../Workload-Factory-API-Samples/list_latency_events | 2 +- 12 files changed, 38 insertions(+), 42 deletions(-) diff --git a/Management-Utilities/Workload-Factory-API-Samples/README.md b/Management-Utilities/Workload-Factory-API-Samples/README.md index 32dbdc0..b5dda71 100644 --- a/Management-Utilities/Workload-Factory-API-Samples/README.md +++ b/Management-Utilities/Workload-Factory-API-Samples/README.md @@ -62,7 +62,7 @@ If you do create a new script, please consider contributing it back to this repo | [link_register](link_register) | This registers a Lambda function as a Workload Factory Link. | | [list_bluexp_accts](list_bluexp_accts) | This list all the BlueXP accounts (a.k.a. organizations) that you have access to. | | [list_bluexp_connectors](list_bluexp_connectors) | This list all the BlueXP connectors that you have access to. | -| [list_bluexp_organization_members](list_organization_members) | This list all members of a provided BlueXP organization. | +| [list_bluexp_organization_members](list_bluexp_organization_members) | This list all members of a provided BlueXP organization. | | [list_bluexp_roles](list_bluexp_roles) | This list all the BlueXP roles that you have access to. | | [list_bluexp_workspaces](list_bluexp_workspaces) | This list all the BlueXP workspaces that you have access to. | | [list_cicd_clones](list_cicd_clones) | This lists all the clones that you have access to in the specified CI/CD project. | diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add index adaf1bf..c5d7c3f 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_add @@ -70,7 +70,7 @@ required_options["ORGANIZATION_NAME"]='Error: You must provide the name of the o check_required_options -if [ -z "$REFRESH_TOKEN" -a -z "$BEATER_TOKEN" ]; then +if [ -z "$REFRESH_TOKEN" -a -z "$BEARER_TOKEN" ]; then echo "Error: You must provide either a refresh token or a bearer token." >&2 usage fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete index f67da76..e0f7da3 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_delete @@ -69,7 +69,7 @@ done declare -A required_options #required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh token is required to run this script. It can be obtained from this web page: # https://services.cloud.netapp.com/refresh-token\n\n' -required_options["ORGANIZATION_ID"]='Error: You must provide the name of the organization you want to delete. +required_options["ORGANIZATION_ID"]='Error: You must provide the ID of the organization you want to delete. You can get the list of organization you have access to by running the "list_bluexp_accts" script found in this GitHub repository: https://github.com/NetApp/FSx-ONTAP-samples-scripts/tree/main/Management-Utilities/Workload-Factory-API-Samples\n\n' diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add index a68bcc6..0db5924 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_add @@ -78,7 +78,7 @@ required_options["emailAddress"]='Error: You must provide the email address of t check_required_options -if [ -z "$REFRESH_TOKEN" -a -z "$BEATER_TOKEN" ]; then +if [ -z "$REFRESH_TOKEN" -a -z "$BEARER_TOKEN" ]; then echo "Error: You must provide either a refresh token or a bearer token." >&2 usage fi diff --git a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete index 4a68a5d..ce6def1 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete +++ b/Management-Utilities/Workload-Factory-API-Samples/bluexp_organization_member_delete @@ -1,22 +1,22 @@ #!/bin/bash # -# This script deletes a user from an BlueXP organization (a.k.a BlueXP account). +# This script deletes a user from an BlueXP organization. # ################################################################################ # Display usage information then exits the script. ################################################################################ usage () { cat 1>&2 <&2 usage -elif [ -z "$USER_ID" -a -z "$SECRET_ARN" ]; then - echo "Error: You must provide either user_id and password OR secret_arn." >&2 +elif [ \( -n "$USER_ID" -o -n "$PASSWORD" \) -a \( -z "$USER_ID" -o -z "$PASSWORD" \) ]; then + echo "Error: Both user_id and password must be provided together." >&2 usage -elif [ -n "$USER_ID" -a -z "$PASSWORD" ]; then - echo "Error: You must provide a password when providing a user_id." >&2 +elif [ -z "$USER_ID" -a -z "$PASSWORD" -a -z "$SECRET_ARN" ]; then + echo "Error: You must provide either user_id and password OR secret_arn." >&2 usage fi # diff --git a/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration index b146c47..5cd3b5d 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration +++ b/Management-Utilities/Workload-Factory-API-Samples/get_latency_configuration @@ -98,16 +98,12 @@ if [ -z "$token" ]; then echo "Error: Failed to obtain an access token. Exiting." >&2 exit 1 fi - +# +# According to the documentation and some testing, this API doesn't support +# pagination, probably because it didn't used to return volume specific +# configurations. URL="https://api.workloads.netapp.com/accounts/${BLUEXP_ACCOUNT_ID}/builders/v1/alarm/configure" run_curl GET "$token" "$URL" $tmpout $tmperr -nextToken=$(jq -r '.nextToken' $tmpout) -while [ "$nextToken" != "null" -a ! -z "$nextToken" ]; do - run_curl GET "$token" "${URL}?nextToken=$nextToken" $tmpout2 $tmperr - cat $tmpout2 >> $tmpout - nextToken=$(jq -r '.nextToken' $tmpout2) -done - if [ "$JSON_OUTPUT" == true ]; then if jq -r . $tmpout; then : diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts index 9fdf0ef..dae5d3b 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_accts @@ -66,13 +66,11 @@ while getopts ht:je:b: opt; do *) usage ;; esac done -# -# Declare an array of required options and the error message to display if they are not set. -declare -A required_options -required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh tokon is required to run this script. It can be obtain from this web page: - https://services.cloud.netapp.com/refresh-token\n\n' -check_required_options +if [ -z "$REFRESH_TOKEN" -a -z "$BEARER_TOKEN" ]; then + echo "Error: You must provide either a refresh token or a bearer token." >&2 + usage +fi # # Check that the required commands are available. for cmd in jq curl; do diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members index 634b068..960f2ca 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_organization_members @@ -15,7 +15,7 @@ usage () { cat >&2 <&2 + usage +fi # # Check that the required commands are available. for cmd in jq curl; do diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles index f816c02..f4faf1a 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles +++ b/Management-Utilities/Workload-Factory-API-Samples/list_bluexp_roles @@ -66,12 +66,11 @@ while getopts ht:je:b: opt; do esac done # -# Declare an array of required options and the error message to display if they are not set. -declare -A required_options -required_options["REFRESH_TOKEN"]='Error: A BlueXP refresh tokon is required to run this script. It can be obtain from this web page: - https://services.cloud.netapp.com/refresh-token\n\n' - -check_required_options +# Check the required parameters. +if [ -z "$REFRESH_TOKEN" -a -z "$BEARER_TOKEN" ]; then + echo "Error: Either a BlueXP refresh token or a Bearer token is required to run this script." >&2 + usage +fi # # Check that the required commands are available. for cmd in jq curl; do diff --git a/Management-Utilities/Workload-Factory-API-Samples/list_latency_events b/Management-Utilities/Workload-Factory-API-Samples/list_latency_events index ebcdef5..7f80e6e 100755 --- a/Management-Utilities/Workload-Factory-API-Samples/list_latency_events +++ b/Management-Utilities/Workload-Factory-API-Samples/list_latency_events @@ -132,5 +132,5 @@ else echo "No latency events were found." exit 0 fi - sort -f $tmpout2 | column -s, -t -N "Timestamnp,File System Name,Volume Name,Volume ID,Region,Alert Type,Event Type,Latency,Median Latency,IOPS,Median Over Percent" + sort -f $tmpout2 | column -s, -t -N "Timestamp,File System Name,Volume Name,Volume ID,Region,Alert Type,Event Type,Latency,Median Latency,IOPS,Median Over Percent" fi From de82d124403f90c37350659629979f4a265baa6a Mon Sep 17 00:00:00 2001 From: Keith Cantrell Date: Wed, 1 Jul 2026 18:13:27 -0500 Subject: [PATCH 16/16] Updated for the new programs --- .github/CODEOWNERS | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 616ede4..c5a4b5a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,9 +3,17 @@ * @kcantrel # # Set the owner for all the individual samples, if the owner is known and they want to continue to maintain the sample: -/CloudFormation/deploy-fsx-ontap/ @kcantrel -/Ansible/fsx_inventory_report/ @kcantrel -/Ansible/snapmirror_report/ @kcantrel +/Infrastructure_as_Code/CloudFormation/deploy-fsx-ontap/ @kcantrel +/Infrastructure_as_Code/CloudFormation/Export-FSxN-CloudFormation/ @kcantrel +/Infrastructure_as_Code/CloudFormation/NetApp-FSxN-Custom-Resources-Samples/ @kcantrel +/Infrastructure_as_Code/Ansible/fsx_inventory_report/ @kcantrel +/Infrastructure_as_Code/Ansible/snapmirror_report/ @kcantrel +/Infrastructure_as_Code/Ansible/Volume_Management/ @kcantrel +/Infrastructure_as_Code/Terraform/Miscellaneous/ @kcantrel +/Infrastructure_as_Code/Terraform/deploy-fsx-ontap-sqlserver/ @varunrai +/Infrastructure_as_Code/Terraform/deploy-fsx-ontap-fileshare-access/ @varunrai +/Infrastructure_as_Code/Terraform/deploy-fsx-ontap/ @kcantrel +/Infrastructure_as_Code/Terraform/fsxn-replicate/ @nichollri /EKS/FSxN-as-PVC-for-EKS/ @mickeysh /EKS/Backup-EKS-Applications-with-Trident-Protect/ @kcantrel /EKS/PV-Migrate-with-Trident-Protect/ @kcantrel @@ -17,12 +25,10 @@ /Management-Utilities/fsxn-rotate-secret/ @kcantrel /Management-Utilities/warm_performance_tier/ @kcantrel /Management-Utilities/Workload-Factory-API-Samples/ @kcantrel -/Monitoring/CloudWatch-FSx/ @kcantrel -/Monitoring/LUN-monitoring/ @kcantrel -/Monitoring/auto-add-cw-alarms/ @kcantrel -/Monitoring/monitor-ontap-services/ @kcantrel -/Monitoring/ingest_nas_audit_logs_into_cloudwatch/ @kcantrel -/Terraform/deploy-fsx-ontap-sqlserver/ @varunrai -/Terraform/deploy-fsx-ontap-fileshare-access/ @varunrai -/Terraform/deploy-fsx-ontap/ @kcantrel -/Terraform/fsxn-replicate/ @nichollri +# +# All the monitoring samples have been moved to their own repo. +#/Monitoring/CloudWatch-FSx/ @kcantrel +#/Monitoring/LUN-monitoring/ @kcantrel +#/Monitoring/auto-add-cw-alarms/ @kcantrel +#/Monitoring/monitor-ontap-services/ @kcantrel +#/Monitoring/ingest_nas_audit_logs_into_cloudwatch/ @kcantrel