前段时间接到来自领导的一个需求,需要对我们在用的 SendGrid 平台实现账号侧(创建 – 配置)自动化流程
经过前期调研已经将官方的API测通
接下来就准备使用 Terraform 完成指定流程
因为 SendGrid 在 Terraform 上并没有发布官方的版本,只能挨个找下最近更新的第三方版本
https://registry.terraform.io/providers/kenzo0107/sendgrid/latest/docs
就在我 tf 代码写好了之后,测试的时候总是返回 api key权限不足,创建不了 subuser, 可是我明明用这个key在postman上是测试成功的,于是只能先怀疑这些 terraform 的供应商(中间换了几个provider都报错)不可用
无奈之下,只能回退到自己编码的局面
#!/usr/bin/env bash
# Author: Weldon
# Date: 2024/08/29
SENDGRID_API_KEY="you_api_key"
base_url="https://api.sendgrid.com"
subuser_name="your_name"
subuser_email="your_email_adress"
subuser_password="your_password"
whitelabel_domain="noreply.yourdomain.com"
branded_link_domain="yourdomain.com"
branded_link_subdomain="mail"
cloudflare_base_url="https://api.cloudflare.com"
cloudflare_access_token="your_cf_token"
cloudflare_zone_id="your_zone_id"
yellow() {
echo -e "\e[33mINFO: $@\e[0m"
}
green() {
echo -e "\e[32m$@\e[0m\n"
}
red() {
echo -e "\e[31mBLOCK: $@\e[0m\n"
}
function user_confirm_before_continue() {
while true; do
read -p "Do you want to continue? (Y/N): " user_input
case $user_input in
[yY] | [yY][eE][sS])
green "Let's move on, continuing..."
return 0
;;
*)
red "Goodbye, exiting..."
exit 1
;;
esac
done
}
function user_confirm_before_delete() {
can_be_delete=0
while true; do
read -p "Do you want to delete the previous one? (Y/N): " user_input
case $user_input in
[yY] | [yY][eE][sS])
green "Start deleting..."
can_be_delete=1
return 0
;;
*)
green "Keep intact, Goodbye..."
exit 1
;;
esac
done
}
function check_if_request_successful() {
if [[ $response_code -eq 200 || $response_code -eq 204 ]]; then
green "---- Congratulations: current http request successful! 👏👏👏"
else
red "---- Unfortunate: current http request failed! ❌❌❌"
exit 1
fi
}
# Cloudflare API
##
function delete_a_cloudflare_dns_record() {
cloudflare_dns_record_id=$1
if [[ -n "${cloudflare_dns_record_id}" ]]; then
yellow "---- Going to delete cloudflare dns record: ${cloudflare_dns_record_id}"
response_code=$(curl -s -L -m 10 -o /dev/null -w "%{http_code}" \
--request DELETE \
--url "${cloudflare_base_url}/client/v4/zones/${cloudflare_zone_id}/dns_records/${cloudflare_dns_record_id}" \
--header 'Content-Type: application/json' \
--header 'X-Auth-Email: ' \
--header "Authorization: Bearer ${cloudflare_access_token}")
check_if_request_successful
fi
}
## List DNS Records
function list_cloudflare_dns_records() {
page=1
while true; do
# Get dns records for the current page
response=$(curl -s -L --request GET \
--url "${cloudflare_base_url}/client/v4/zones/${cloudflare_zone_id}/dns_records?type=CNAME&per_page=5&page=${page}" \
--header 'Content-Type: application/json' \
--header 'X-Auth-Email: ' \
--header "Authorization: Bearer ${cloudflare_access_token}")
# Check if there are no dns records or we've reached the last page
if [[ $(jq '.result | length' <<<"$response") -eq 0 ]]; then
break
fi
# Extract dns records host/data and output
jq -r '.result[] | select(.comment == "Domain verification record")| .id + " " + .name + " " + .content' <<<"$response"
((page++))
done
}
function check_cloudflare_dns_records_existed() {
current_cloudflare_dns_recoed_file=./cloudflare_dns_records_$(date '+%Y-%m-%d-%H:%M:%S')
yellow "---- All of Cloudflare dns records:"
list_cloudflare_dns_records | tee $current_cloudflare_dns_recoed_file
expect_content=$1
good_to_go=0
# yellow "---- All of Cloudflare dns records:\n $(cat $current_cloudflare_dns_recoed_file)"
dns_record_host=$(echo "$expect_content" | awk '{print $1}')
# if [[ -z $(echo $all_dns_content | jq -r '.result[].name + " " + .result[].content' | grep -w "$dns_record_host") ]]; then
# if [[ -z $(echo $all_dns_content | jq -r '.result[].name + " " + .result[].content' | grep -w "$expect_content") ]]; then
if [[ -z $(cat "$current_cloudflare_dns_recoed_file" | grep -w "$dns_record_host") ]]; then
green "---- Record has not been created before with host: $dns_record_host"
good_to_go=1
else
red "---- Previous record already existed with host : $dns_record_host"
user_confirm_before_delete
if [[ $can_be_delete -eq 1 ]]; then
yellow "Let's delete it."
# dns_record_id=$(echo $all_dns_content | jq -r ".result[] | select(.name==\"$dns_record_host\") | .id")
dns_record_id=$(cat "$current_cloudflare_dns_recoed_file" | grep -w "$dns_record_host" | awk '{print $1}')
delete_a_cloudflare_dns_record "$dns_record_id"
if [ $? -eq 0 ]; then
yellow "Let's move on."
good_to_go=1
fi
fi
fi
}
## Create DNS Record
function create_cloudflare_dns_record() {
[[ -z "$cloudflare_zone_id" ]] && echo "cloudflare_zone_id is empty, exiting..." && exit 1
# green "---- inner cloudflare func - current sendgrid dns record count: ${#dns_records[@]}"
if [[ ${#dns_records[@]} -gt 0 ]]; then
yellow "------------------------------------Start to create clareflare dns record------------------------------------"
# "${!dns_records[@]}" 和 "${dns_records[@]}" 的区别,它们分别用于获取关联数组的键和值:
# "${!dns_records[@]}":这将获取关联数组 dns_records 的所有键。在我们的例子中,这些键是 host。
# "${dns_records[@]}":这将获取关联数组 dns_records 的所有值。在我们的例子中,这些值是 data。
for host in "${!dns_records[@]}"; do
yellow "---- Current host is: $host"
yellow "---- Current data is: ${dns_records[$host]}"
go_to_check="${host} ${dns_records[$host]}"
check_cloudflare_dns_records_existed "$go_to_check"
if [[ $good_to_go -eq 1 ]]; then
response_by_create_cloudflare_dns_record=$(curl -s -L -m 10 \
-X POST \
"${cloudflare_base_url}/client/v4/zones/${cloudflare_zone_id}/dns_records" \
--header 'Content-Type: application/json' \
--header 'X-Auth-Email: ' \
--header "Authorization: Bearer ${cloudflare_access_token}" \
--data '{
"content": '\"${dns_records[$host]}\"',
"name": '\"${host}\"',
"proxied": false,
"type": "CNAME",
"comment": "Domain verification record",
"id": "023e105f4ecef8ad9ca31a8372d0c355",
"tags": [],
"ttl": 3600
}')
yellow "---- Result of create cloudflare dns record: \n $response_by_create_cloudflare_dns_record"
fi
done
else
red "---- Not found Cloudflare dns datas given by SendGrid, exiting..."
exit 1
fi
}
# Settings - Subusers
## List all Subusers
function check_subuser_existed() {
expected_subuser=$1
yellow "---- Going to get all subusers"
all_users=$(curl -s "${base_url}/v3/subusers" --header "Authorization: Bearer ${SENDGRID_API_KEY}" | jq -r .[].username)
yellow "SendGrid all subusers:\n $all_users"
if [[ -z $(echo $all_users | grep -w $expected_subuser) ]]; then
green "---- Subuser has not been created before: $expected_subuser"
else
red "---- Previous subuser already existed: $expected_subuser"
user_confirm_before_delete
if [[ $can_be_delete -eq 1 ]]; then
yellow "Let's delete it."
delete_a_subuser
if [ $? -eq 0 ]; then
yellow "Let's move on."
good_to_go=1
fi
fi
fi
}
## Retrieve all assigned IPs
function retrieve_all_assigned_ips() {
yellow "---- Going to get all assigned ips"
all_assigned_ips=$(curl -s -X GET "${base_url}/v3/ips/assigned" --header "Authorization: Bearer ${SENDGRID_API_KEY}" | jq -r .[].ip)
yellow "---- Current all assigned ips: \n$all_assigned_ips"
dedicated_ip=$(echo $all_assigned_ips | awk '{print $1}')
if [[ -n "${dedicated_ip}" ]]; then
yellow "---- Going to use the first assigned ip: $dedicated_ip"
else
red "---- Not found assigned ip at main account side, exiting..."
exit 1
fi
}
## Create Subuser
function create_a_subuser() {
[[ -z "$subuser_name" ]] && echo "subuser_name is empty, exiting..." && exit 1 || [[ -z "$subuser_email" ]] && echo "subuser_email is empty, exiting..." && exit 1 || [[ -z "$subuser_password" ]] && echo "subuser_password is empty, exiting..." && exit 1
check_subuser_existed "${subuser_name}"
retrieve_all_assigned_ips
yellow "---- Going to create subuser: ${subuser_name}"
subuser_info=$(curl -s -X POST \
"${base_url}/v3/subusers" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--data-raw '{
"username": '\"${subuser_name}\"',
"email": '\"${subuser_email}\"',
"password": '\"${subuser_password}\"',
"ips": [
'\"${dedicated_ip}\"'
],
"region": "global",
"include_region": true
}')
yellow "---- Subuser(${subuser_name}) information:\n $subuser_info"
if [[ -n $(echo $subuser_info | grep -i error) ]]; then
red "Create useruser failed, please check it manually..."
exit 1
else
if [[ -z "echo ${subuser_info}" ]]; then
echo "string is not empty"
fi
subuser_id=$(echo $subuser_info | jq -r .user_id)
green "${subuser_name}'s user_id is: $subuser_id"
fi
}
## Delete a subuser
function delete_a_subuser() {
yellow "---- Going to delete subuser: ${subuser_name}"
response_code=$(curl -s -L -m 10 -o /dev/null -w "%{http_code}" \
--request DELETE "${base_url}/v3/subusers/${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
check_if_request_successful
}
## Retrieve Subuser Reputations
function retrieve_subuser_reputations() {
yellow "---- Going to get reputations for: ${subuser_name}"
curl -s "${base_url}/v3/subusers/reputations?usernames=${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}"
}
## Enable/disable a subuser
function enable_disable_a_subuser() {
action=$1
yellow "---- Going to $action ${subuser_name}"
user_confirm_before_continue
is_disabled=false
if [[ $action == 'enable' ]]; then
is_disabled=false
elif [[ $action == 'disable' ]]; then
is_disabled=true
fi
curl -s --request PATCH "${base_url}/v3/subusers/${subuser_name}" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--data "{
\"disabled\": $is_disabled
}"
}
# Security - Api key
## Create API keys
function create_sendgrid_api_keys() {
yellow "---- Going to create sendgrid api key for: ${subuser_name}"
SUBUSER_API_KEY_NAME="Notification Key"
retrieve_all_api_keys_belonging_to_a_authenticated_user "$SUBUSER_API_KEY_NAME"
response_by_create_sendgrid_api_keys=$(curl -s -X POST \
"${base_url}/v3/api_keys" \
--header "on-behalf-of: ${subuser_name}" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--data "{
\"name\": \"$SUBUSER_API_KEY_NAME\",
\"scopes\": [
\"mail.send\",
\"alerts.create\",
\"alerts.read\"
]
}")
echo $response_by_create_sendgrid_api_keys >>./${subuser_name}_sendgrid_api_keys.secret
echo -e "---- Create at $(date '+%Y-%m-%d %H:%M:%S') \n" >>./${subuser_name}_sendgrid_api_keys.secret
}
## Retrieve all API Keys belonging to the authenticated user
function retrieve_all_api_keys_belonging_to_a_authenticated_user() {
api_key_name=$1
yellow "---- Going to get all sendgrid api keys belonging to: ${subuser_name}"
all_api_keys_with_a_subuser=$(curl -s "${base_url}/v3/api_keys" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
if [[ -n $(echo $all_api_keys_with_a_subuser | grep -i error) ]]; then
red "Get all sendgrid api keys failed, please check it manually..."
exit 1
else
yellow "---- All Api keys for ${subuser_name}: $all_api_keys_with_a_subuser"
if [[ -z $(echo $all_api_keys_with_a_subuser | grep -w "$api_key_name") ]]; then
green "---- Api key has not been created before: $api_key_name"
else
red "---- Previous Api key already existed: $api_key_name"
user_confirm_before_delete
if [[ $can_be_delete -eq 1 ]]; then
yellow "Let's delete it."
api_key_id=$(echo $all_api_keys_with_a_subuser | jq -r ".result[] | select(.name==\"$api_key_name\") | .api_key_id")
delete_a_sendgrid_api_keys "$api_key_id"
if [ $? -eq 0 ]; then
yellow "Let's move on."
good_to_go=1
fi
fi
fi
fi
}
## Delete API keys
function delete_a_sendgrid_api_keys() {
expected_api_key_id=$1
yellow "---- Going to delete a sendgrid api key belonging to: ${subuser_name}"
response_code=$(curl -s -L -m 10 -o /dev/null -w "%{http_code}" \
--globoff --request DELETE "${base_url}/v3/api_keys/${expected_api_key_id}" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
check_if_request_successful
}
# Deliverability - Domain Authentication
## List all authenticated domains
function list_authenticated_domains() {
yellow "---- Going to get all authenticated domains for: ${subuser_name}"
expected_authenticated_domain=$1
all_authenticated_domains=$(curl -s "${base_url}/v3/whitelabel/domains" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
if [[ -n $(echo $all_authenticated_domains | grep -i error) ]]; then
red "Get all authenticated domains failed, please check it manually..."
exit 1
else
yellow "---- all authenticated domain for ${subuser_name}:\n $all_authenticated_domains"
if [[ -z $(echo $all_authenticated_domains | grep -w $expected_authenticated_domain) ]]; then
green "---- Authenticated domain has not been created before: $expected_authenticated_domain"
else
red "---- Previous authenticated domain already existed: $expected_authenticated_domain"
user_confirm_before_delete
if [[ $can_be_delete -eq 1 ]]; then
yellow "Let's delete it."
authenticated_domain_id=$(echo $all_authenticated_domains | jq -r ".[] | select(.domain==\"$expected_authenticated_domain\") | .id")
delete_an_authenticated_domain $authenticated_domain_id
if [ $? -eq 0 ]; then
yellow "Let's move on."
good_to_go=1
fi
fi
fi
fi
}
## Authenticate a domain
function authenticate_a_domain() {
yellow "---- Going to add an authenticated domain \e[0;35m${whitelabel_domain}\e[0m for: ${subuser_name}"
list_authenticated_domains "${whitelabel_domain}"
response=$(curl -s -X POST \
"${base_url}/v3/whitelabel/domains" \
--header "on-behalf-of: ${subuser_name}" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--data "{
\"domain\": \"${whitelabel_domain}\",
\"default\": true
}")
if [[ -n $(echo $response | grep -i error) ]]; then
red "Add an authenticated domain failed, please check it manually..."
exit 1
else
yellow "---- Response by authenticate a domain: $response"
authenticated_domain_id=$(echo "$response" | jq -r '.id')
yellow "---- Authenticated domain ID: $authenticated_domain_id"
for entry in mail_cname dkim1 dkim2; do
sendgrid_dns_host=$(echo "$response" | jq -r ".dns.${entry}.host")
sendgrid_dns_data=$(echo "$response" | jq -r ".dns.${entry}.data")
dns_records["$sendgrid_dns_host"]="$sendgrid_dns_data"
done
# green "---- inner sendgrid func - current sendgrid dns record count: ${#dns_records[@]}"
fi
}
## Validate a domain authentication.
function validate_a_domain_authentication() {
yellow "---- Going to validate a domain authentication \e[0;35m${whitelabel_domain}\e[0m for: ${subuser_name}"
if [[ -n "${authenticated_domain_id}" ]]; then
result_of_validate_domain_authentication=$(curl -s -X POST \
"${base_url}/v3/whitelabel/domains/${authenticated_domain_id}/validate" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
yellow "---- Result of validate domain authentication: $result_of_validate_domain_authentication"
if $(echo "$result_of_validate_domain_authentication" | jq -r .valid); then
green "---- Great, domain authentication have been set up successfully."
else
red "---- Sadly, domain authentication set up failed."
fi
else
red "Not found authenticated_domain_id, exiting..."
exit 1
fi
}
## Delete an authenticated domain
function delete_an_authenticated_domain() {
expected_authenticated_domain_id=$1
yellow "---- Going to delete an authenticated domain for: ${subuser_name}"
if [[ -n "${expected_authenticated_domain_id}" ]]; then
response_code=$(curl -s -L -m 10 -o /dev/null -w "%{http_code}" \
--request DELETE "${base_url}/v3/whitelabel/domains/${expected_authenticated_domain_id}" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
check_if_request_successful
else
red "Not found authenticated_domain_id, exiting..."
exit 1
fi
}
# Deliverability - Link Branding
## Retrieve all branded links
function retrieve_all_branded_links() {
expected_branded_link_subdomain=$1
expected_branded_link_domain=$2
yellow "---- Going to get all branded links for: ${subuser_name}"
all_branded_links=$(curl -s "${base_url}/v3/whitelabel/links" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
yellow "---- all branded links for ${subuser_name}:\n $all_branded_links"
if [[ -z $(echo $all_branded_links | grep -w $expected_branded_link_subdomain | grep -w $expected_branded_link_domain) ]]; then
green "---- Branded link has not been created before: ${expected_branded_link_subdomain}.${expected_branded_link_domain}"
else
red "---- Previous branded link already existed: ${expected_branded_link_subdomain}.${expected_branded_link_domain}"
user_confirm_before_delete
if [[ $can_be_delete -eq 1 ]]; then
yellow "Let's delete it."
previous_branded_link_id=$(echo $all_branded_links | jq -r ".[] | select(.subdomain==\"$expected_branded_link_subdomain\" and .domain==\"$expected_branded_link_domain\") | .id")
delete_a_branded_link "$previous_branded_link_id"
if [ $? -eq 0 ]; then
yellow "Let's move on."
good_to_go=1
fi
fi
fi
}
## Create a branded link
function create_a_branded_link() {
if [[ -n "${branded_link_domain}" && -n "${branded_link_subdomain}" ]]; then
retrieve_all_branded_links "${branded_link_subdomain}" "${branded_link_domain}"
yellow "---- Going to create a branded link \e[0;35m${branded_link_subdomain}\e[0m for: ${subuser_name}"
reponse_by_create_a_branded_link=$(curl -s -X POST \
"${base_url}/v3/whitelabel/links" \
--header "on-behalf-of: ${subuser_name}" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}" \
--data '{
"domain": '\"${branded_link_domain}\"',
"subdomain": '\"${branded_link_subdomain}\"',
"default": true
}')
else
red "Not found branded_link_domain or branded_link_subdomain, exiting..."
exit 1
fi
if [[ -n $(echo $reponse_by_create_a_branded_link | grep -i error) ]]; then
red "Add an authenticated domain failed, please check it manually..."
exit 1
else
yellow "---- Response by create a branded link: $reponse_by_create_a_branded_link"
branded_link_id=$(echo "$reponse_by_create_a_branded_link" | jq -r '.id')
yellow "---- Authenticated domain ID: $branded_link_id"
for entry in domain_cname owner_cname; do
sendgrid_dns_host=$(echo "$reponse_by_create_a_branded_link" | jq -r ".dns.${entry}.host")
sendgrid_dns_data=$(echo "$reponse_by_create_a_branded_link" | jq -r ".dns.${entry}.data")
dns_records["$sendgrid_dns_host"]="$sendgrid_dns_data"
done
# green "---- inner sendgrid func - current sendgrid dns record count: ${#dns_records[@]}"
fi
}
## Validate a branded link
function validate_a_branded_link() {
yellow "---- Going to validate a branded link for: ${subuser_name}"
if [[ -n "${branded_link_id}" ]]; then
result_of_validate_branded_link=$(curl -s -X POST \
"${base_url}/v3/whitelabel/links/${branded_link_id}/validate" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
yellow "---- Result of validate branded link: $result_of_validate_branded_link"
if $(echo "$result_of_validate_branded_link" | jq -r .valid); then
green "---- Great, branded link have been set up successfully."
else
red "---- Sadly, branded link set up failed."
fi
else
red "Not found branded_link_id, exiting..."
exit 1
fi
}
## Delete a branded link
function delete_a_branded_link() {
expected_branded_link_id=$1
yellow "---- Going to delete a branded link for: ${subuser_name}"
if [[ -n "${expected_branded_link_id}" ]]; then
response_code=$(curl -s -L -m 10 -o /dev/null -w "%{http_code}" \
--globoff --request DELETE "${base_url}/v3/whitelabel/links/${expected_branded_link_id}" \
--header "on-behalf-of: ${subuser_name}" \
--header "Authorization: Bearer ${SENDGRID_API_KEY}")
check_if_request_successful
else
red "Not found branded_link_id, exiting..."
exit 1
fi
}
# main process to create a subuser
function main() {
declare -A dns_records=()
create_a_subuser
create_sendgrid_api_keys
authenticate_a_domain
# Here, requires going to cloudflare to set up DNS and waiting for a while
create_cloudflare_dns_record
sleep 60
validate_a_domain_authentication
unset dns_records
declare -A dns_records=()
create_a_branded_link
# Here, requires going to cloudflare to set up DNS and waiting for a while
create_cloudflare_dns_record
sleep 60
validate_a_branded_link
retrieve_subuser_reputations
# enable_disable_a_subuser disable
# enable_disable_a_subuser enable
}
main
局部截图
本脚本涉及逻辑诸多
- 创建 SendGrid subuser
- 检查是否已存在
- 用户确认删除
- 创建 SendGrid API key
- 检查是否已存在
- 用户确认删除
- 创建 SendGrid 验证域名
- 检查是否已存在
- 用户确认删除
- 创建 Cloudflare dns record
- 检查是否已存在
- 用户确认删除
- 验证 SendGrid 验证域名
- 创建 SendGrid 品牌链接
- 检查是否已存在
- 用户确认删除
- 创建 Cloudflare dns record
- 检查是否已存在
- 用户确认删除
- 验证 SendGrid 品牌链接
- 获取 SendGrid subuser 声誉度