diff --git a/CHANGELOG.md b/CHANGELOG.md index 850577e9..4f6ccfda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,16 @@ * [UPGRADE] OpenSearch Data Source Plugin to Grafana upgraded from 2.28.0 to 2.29.1 * **Metrics** - * [CHANGE] The Grafana alerts targeting SAS Viya that previously were provided by default have been moved to the samples directory. Given the variability of SAS Viya environments, these alerts are now optional. They can be copied to USER_DIR/monitoring/alerting and customized to fit the SAS Viya environment prior to deployment. They have also been split into separate files for easier customization. See the [Alerting Samples README](samples/alerts/README.md) for more details. + * [FEATURE] Automatically define the SMTP server configuration to permit Grafana to send e-mails. +If enabled (by setting `AUTOGENERATE_SMTP` to 'true'), this optional feature allows admins to define +email-based contact points for alerts. Users need to provide connection information via the environment +variables: `SMTP_SERVER, SMTP_PORT`, `SMTP_FROM_ADDRESS` and `SMTP_FROM_NAME`. See [Configure Email Settings for Grafana Alerts](https://documentation.sas.com/doc/en/obsrvcdc/v_003/obsrvdply/n0auhd4hutsf7xn169hfvriysz4e.htm#p1fql9ekyxckamn1jtlujeauhy72) +for more information. + * [CHANGE] The Grafana alerts targeting SAS Viya that previously were provided by default have been moved +to the samples directory. Given the variability of SAS Viya environments, these alerts are now optional. +They can be copied to USER_DIR/monitoring/alerting and customized to fit the SAS Viya environment prior +to deployment. They have also been split into separate files for easier customization. See the [Alerting Samples README](samples/alerts/README.md) for more details. + ## Version 1.2.41 (19AUG2025) * **Metrics** diff --git a/bin/autogenerate-include.sh b/bin/autogenerate-include.sh index 6349f046..e3e7b0bf 100644 --- a/bin/autogenerate-include.sh +++ b/bin/autogenerate-include.sh @@ -6,118 +6,177 @@ function checkYqVersion { - # confirm yq installed and correct version - local goodver yq_version - goodver="yq \(.+mikefarah.+\) version (v)?(4\.(3[2-9]|[4-9][0-9])\..+)" - yq_version=$(yq --version) - if [ "$?" == "1" ]; then - log_error "Required component [yq] not available." - return 1 - elif [[ ! $yq_version =~ $goodver ]]; then - log_error "Incorrect version [$yq_version] found; version 4.32.2+ required." - return 1 - else - log_debug "A valid version [$yq_version] of yq detected" - return 0 - fi + # confirm yq installed and correct version + local goodver yq_version + goodver="yq \(.+mikefarah.+\) version (v)?(4\.(3[2-9]|[4-9][0-9])\..+)" + yq_version=$(yq --version) + if [ "$?" == "1" ]; then + log_error "Required component [yq] not available." + return 1 + elif [[ ! $yq_version =~ $goodver ]]; then + log_error "Incorrect version [$yq_version] found; version 4.32.2+ required." + return 1 + else + log_debug "A valid version [$yq_version] of yq detected" + return 0 + fi } export -f checkYqVersion function create_ingress_certs { - local certFile keyFile namespace secretName - - namespace="$1" - secretName="$2" - certFile="${3:-$INGRESS_CERT}" - keyFile="${4:-$INGRESS_KEY}" - - if [ -f "$certFile" ] && [ -f "$keyFile" ]; then - kubectl delete secret "$secretName" --namespace "$namespace" --ignore-not-found - kubectl create secret tls "$secretName" --namespace "$namespace" --key="$keyFile" --cert="$certFile" - kubectl -n $namespace label secret $secretName managed-by="v4m-es-script" - elif [ ! -z "$certFile$keyFile" ]; then - log_warn "Missing Ingress certificate file; specified Ingress cert [$certFile] and/or key [$keyFile] file is missing." - log_warn "Create the missing Kubernetes secrets after deployment; use command: kubectl -create secret tls $secretName --namespace $namespace --key=cert_key_file --cert=cert_file" - fi + local certFile keyFile namespace secretName + + namespace="$1" + secretName="$2" + certFile="${3:-$INGRESS_CERT}" + keyFile="${4:-$INGRESS_KEY}" + + if [ -f "$certFile" ] && [ -f "$keyFile" ]; then + kubectl delete secret "$secretName" --namespace "$namespace" --ignore-not-found + kubectl create secret tls "$secretName" --namespace "$namespace" --key="$keyFile" --cert="$certFile" + kubectl -n $namespace label secret $secretName managed-by="v4m-es-script" + elif [ ! -z "$certFile$keyFile" ]; then + log_warn "Missing Ingress certificate file; specified Ingress cert [$certFile] and/or key [$keyFile] file is missing." + log_warn "Create the missing Kubernetes secrets after deployment; use command: kubectl -create secret tls $secretName --namespace $namespace --key=cert_key_file --cert=cert_file" + fi } export -f create_ingress_certs AUTOGENERATE_INGRESS="${AUTOGENERATE_INGRESS:-false}" AUTOGENERATE_STORAGECLASS="${AUTOGENERATE_STORAGECLASS:-false}" +AUTOGENERATE_SMTP="${AUTOGENERATE_SMTP:-false}" -if [ "$AUTOGENERATE_INGRESS" != "true" ] && [ "$AUTOGENERATE_STORAGECLASS" != "true" ]; then - log_debug "No autogeneration of YAML enabled" - export AUTOGENERATE_SOURCED="NotNeeded" +if [ "$AUTOGENERATE_INGRESS" != "true" ] && [ "$AUTOGENERATE_STORAGECLASS" != "true" ] && [ "$AUTOGENERATE_SMTP" != "true" ]; then + log_debug "No autogeneration of YAML enabled" + export AUTOGENERATE_SOURCED="NotNeeded" fi if [ -z "$AUTOGENERATE_SOURCED" ]; then - if ! checkYqVersion; then - exit 1 - fi - - if [ "$AUTOGENERATE_INGRESS" == "true" ]; then - - # Confirm NOT on OpenShift - if [ "$OPENSHIFT_CLUSTER" == "true" ]; then - log_error "Setting AUTOGENERATE_INGRESS to 'true' is not valid on OpenShift clusters." - log_error "Web applications will be made accessible via OpenShift routes instead (if enabled)." - - export AUTOGENERATE_INGRESS="false" - exit 1 - fi - - - #validate required inputs - BASE_DOMAIN="${BASE_DOMAIN}" - if [ -z "$BASE_DOMAIN" ]; then - log_error "Required parameter [BASE_DOMAIN] not provided" - exit 1 - fi - - ROUTING="${ROUTING:-host}" - - if [ "$ROUTING" == "path" ]; then - export MON_TLS_PATH_INGRESS="true" - log_debug "Path ingress requested, setting MON_TLS_PATH_INGRESS to 'true'" - elif [ "$ROUTING" != "host" ] && [ "$ROUTING" != "path" ]; then - log_error "Invalid ROUTING value, valid values are 'host' or 'path'" - exit 1 - fi - - INGRESS_CERT="${INGRESS_CERT}" - INGRESS_KEY="${INGRESS_KEY}" - if [ "$INGRESS_CERT/$INGRESS_KEY" != "/" ]; then - if [ ! -f "$INGRESS_CERT" ] || [ ! -f "$INGRESS_KEY" ]; then - # Only WARN b/c missing cert doesn't prevent deployment and it can be created afterwards - log_warn "Missing Ingress certificate file; specified Ingress cert [$INGRESS_CERT] and/or key [$INGRESS_KEY] file is missing." - log_warn "You can create the missing Kubernetes secrets after deployment. See Enable TLS for Ingress topic in Help Center documentation." - #unset variable values to prevent further attempted use - unset INGRESS_CERT - unset INGRESS_KEY - else - log_debug "Ingress cert [$INGRESS_CERT] and key [$INGRESS_KEY] files exist." - fi - fi - - log_info "Autogeneration of Ingress definitions has been enabled" - - fi - - if [ "$AUTOGENERATE_STORAGECLASS" == "true" ]; then - - log_info "Autogeneration of StorageClass specfication has been enabled" - - fi - - export AUTOGENERATE_SOURCED="true" - -elif [ "$AUTOGENERATE_SOURCED" == "NotNeeded" ]; then - log_debug "autogenerate-include.sh not needed" -else - log_debug "autogenerate-include.sh was already sourced [$AUTOGENERATE_SOURCED]" + if ! checkYqVersion; then + exit 1 + fi + + if [ "$AUTOGENERATE_INGRESS" == "true" ]; then + + # Confirm NOT on OpenShift + if [ "$OPENSHIFT_CLUSTER" == "true" ]; then + log_error "Setting AUTOGENERATE_INGRESS to 'true' is not valid on OpenShift clusters." + log_error "Web applications will be made accessible via OpenShift routes instead (if enabled)." + + export AUTOGENERATE_INGRESS="false" + exit 1 + fi + + #validate required inputs + BASE_DOMAIN="${BASE_DOMAIN}" + if [ -z "$BASE_DOMAIN" ]; then + log_error "Required parameter [BASE_DOMAIN] not provided" + exit 1 + fi + + ROUTING="${ROUTING:-host}" + + if [ "$ROUTING" == "path" ]; then + export MON_TLS_PATH_INGRESS="true" + log_debug "Path ingress requested, setting MON_TLS_PATH_INGRESS to 'true'" + elif [ "$ROUTING" != "host" ] && [ "$ROUTING" != "path" ]; then + log_error "Invalid ROUTING value, valid values are 'host' or 'path'" + exit 1 + fi + + INGRESS_CERT="${INGRESS_CERT}" + INGRESS_KEY="${INGRESS_KEY}" + if [ "$INGRESS_CERT/$INGRESS_KEY" != "/" ]; then + if [ ! -f "$INGRESS_CERT" ] || [ ! -f "$INGRESS_KEY" ]; then + # Only WARN b/c missing cert doesn't prevent deployment and it can be created afterwards + log_warn "Missing Ingress certificate file; specified Ingress cert [$INGRESS_CERT] and/or key [$INGRESS_KEY] file is missing." + log_warn "You can create the missing Kubernetes secrets after deployment. See Enable TLS for Ingress topic in Help Center documentation." + #unset variable values to prevent further attempted use + unset INGRESS_CERT + unset INGRESS_KEY + else + log_debug "Ingress cert [$INGRESS_CERT] and key [$INGRESS_KEY] files exist." + fi + fi + + log_info "Autogeneration of Ingress definitions has been enabled" + + fi + + if [ "$AUTOGENERATE_STORAGECLASS" == "true" ]; then + log_info "Autogeneration of StorageClass specfication has been enabled" + fi + + if [ "$AUTOGENERATE_SMTP" == "true" ]; then + + #required + # shellcheck disable=SC2269 + SMTP_HOST="${SMTP_HOST}" + # shellcheck disable=SC2269 + SMTP_PORT="${SMTP_PORT}" + # shellcheck disable=SC2269 + SMTP_FROM_ADDRESS="${SMTP_FROM_ADDRESS}" + # shellcheck disable=SC2269 + SMTP_FROM_NAME="${SMTP_FROM_NAME}" + + #optional + # shellcheck disable=SC2269 + SMTP_USER="${SMTP_USER}" + # shellcheck disable=SC2269 + SMTP_PASSWORD="${SMTP_PASSWORD}" + SMTP_USER_SECRET="${SMTP_USER_SECRET:-grafana-smtp-user}" + SMTP_SKIP_VERIFY="${SMTP_SKIP_VERIFY:-false}" + SMTP_TLS_CERT_FILE="${SMTP_TLS_CERT_FILE:-/cert/tls.crt}" + SMTP_TLS_KEY_FILE="${SMTP_TLS_KEY_FILE:-/cert/tls.key}" + + log_info "Autogeneration of SMTP Configuration has been enabled" + + if [ -z "$SMTP_HOST" ]; then + log_error "Required parameter [SMTP_HOST] not provided" + exit 1 + fi + + if [ -z "$SMTP_PORT" ]; then + log_error "Required parameter [SMTP_PORT] not provided" + exit 1 + fi + + if [ -z "$SMTP_FROM_ADDRESS" ]; then + log_error "Required parameter [SMTP_FROM_ADDRESS] not provided" + exit 1 + fi + + if [ -z "$SMTP_FROM_NAME" ]; then + log_error "Required parameter [SMTP_FROM_NAME] not provided" + exit 1 + fi + + # Handle SMTP user credentials + if [ -n "$(kubectl get secret -n "$MON_NS" "$SMTP_USER_SECRET" --ignore-not-found -o name 2> /dev/null)" ]; then + log_debug "Secret [$SMTP_USER_SECRET] exists; will use it for SMTP user credentials" + elif [ -z "$SMTP_USER" ] && [ -z "$SMTP_PASSWORD" ]; then + log_debug "Neither SMTP_USER nor SMTP_PASSWORD are set; skipping creation of secret [$SMTP_USER_SECRET]" + elif [ -z "$SMTP_USER" ] || [ -z "$SMTP_PASSWORD" ]; then + log_error "Complete SMTP Credentials NOT provided; MUST provide BOTH [SMTP_USER] and [SMTP_PASSWORD]" + log_info "SMTP_USER is set to [$SMTP_USER] and SMTP_PASSWORD is set to [$SMTP_PASSWORD]" + exit 1 + else + log_debug "Secret [$MON_NS/$SMTP_USER_SECRET] will need to be created later." + # shellcheck disable=SC2034 + smtpCreateUserSecret="true" + fi + + fi + + export AUTOGENERATE_SOURCED="true" + + elif [ "$AUTOGENERATE_SOURCED" == "NotNeeded" ]; then + log_debug "autogenerate-include.sh not needed" + else + log_debug "autogenerate-include.sh was already sourced [$AUTOGENERATE_SOURCED]" fi @@ -131,19 +190,17 @@ function checkStorageClass { storageClass="${2:-$STORAGECLASS}" if [ -z "$storageClass" ]; then - log_error "Required parameter not provided. Either [$storageClassEnvVar] or [STORAGECLASS] MUST be provided." - exit 1 + log_error "Required parameter not provided. Either [$storageClassEnvVar] or [STORAGECLASS] MUST be provided." + exit 1 else - if $(kubectl get storageClass "$storageClass" -o name &>/dev/null); then - log_debug "The specified StorageClass [$storageClass] exists" - else - log_error "The specified StorageClass [$storageClass] does NOT exist" - exit 1 - fi + if $(kubectl get storageClass "$storageClass" -o name &>/dev/null); then + log_debug "The specified StorageClass [$storageClass] exists" + else + log_error "The specified StorageClass [$storageClass] does NOT exist" + exit 1 + fi fi } - - export -f checkStorageClass diff --git a/monitoring/bin/deploy_monitoring_cluster.sh b/monitoring/bin/deploy_monitoring_cluster.sh index 5388ce85..2f7f53ff 100755 --- a/monitoring/bin/deploy_monitoring_cluster.sh +++ b/monitoring/bin/deploy_monitoring_cluster.sh @@ -76,6 +76,52 @@ if [ -z "$(kubectl get ns "$MON_NS" -o name 2> /dev/null)" ]; then disable_sa_token_automount "$MON_NS" default fi +AUTOGENERATE_SMTP="${AUTOGENERATE_SMTP:-false}" + +if [ "$AUTOGENERATE_SMTP" == "true" ]; then + + if [ ! -f "$autogeneratedYAMLFile" ]; then + log_debug "Creating file [$autogeneratedYAMLFile]" + touch "$autogeneratedYAMLFile" + else + log_debug "File [$autogeneratedYAMLFile] already exists" + fi + + smtpHost="${SMTP_HOST}" + smtpPort="${SMTP_PORT}" + smtpFromAddress="${SMTP_FROM_ADDRESS}" + smtpFromName="${SMTP_FROM_NAME}" + smtpUserSecret="${SMTP_USER_SECRET}" + smtpSkipVerify="${SMTP_SKIP_VERIFY:-false}" + smtpTLSCertFile="${SMTP_TLS_CERT_FILE:-/cert/tls.crt}" + smtpTLSKeyFile="${SMTP_TLS_KEY_FILE:-/cert/tls.key}" + + # Create secret to hold SMTP user credentials + if [ "$smtpCreateUserSecret" == "true" ]; then + log_debug "Creating secret [$MON_NS/$SMTP_USER_SECRET] from supplied user [$SMTP_USER] and password." + kubectl create secret generic "$SMTP_USER_SECRET" -n "$MON_NS" --from-literal=user="$SMTP_USER" --from-literal=password="$SMTP_PASSWORD" + fi + + yq -i '.grafana."grafana.ini".smtp.enabled=true' "$autogeneratedYAMLFile" + + value="$smtpHost:$smtpPort" yq -i '.grafana."grafana.ini".smtp.host=env(value)' "$autogeneratedYAMLFile" + + value="$smtpFromAddress" yq -i '.grafana."grafana.ini".smtp.from_address=env(value)' "$autogeneratedYAMLFile" + value="$smtpFromName" yq -i '.grafana."grafana.ini".smtp.from_name=env(value)' "$autogeneratedYAMLFile" + + if [ -n "$smtpUserSecret" ]; then + value="$smtpUserSecret" yq -i '.grafana.smtp.existingSecret=env(value)' "$autogeneratedYAMLFile" + fi + + value="$smtpSkipVerify" yq -i '.grafana."grafana.ini".smtp.skip_verify=env(value)' "$autogeneratedYAMLFile" + + value="$smtpTLSCertFile" yq -i '.grafana."grafana.ini".smtp.cert_file=env(value)' "$autogeneratedYAMLFile" + value="$smtpTLSKeyFile" yq -i '.grafana."grafana.ini".smtp.key_file=env(value)' "$autogeneratedYAMLFile" + +else + log_debug "Auto-generation of SMTP not enabled; skipping Grafana SMTP server configuration" +fi + #Generate yaml file with all container-related keys generateImageKeysFile "$PROMOP_FULL_IMAGE" "monitoring/prom-operator_container_image.template" if [ -z "$PROM_OPERATOR_CRD_VERSION" ]; then