公司目前有一个新的需求,就是在上线新的环境的时候,需要在apollo启动后,直接用shell脚本一键部署对应配置,大多数情况下都是搞java,没有怎么接触过shell脚本,临时参考和学习了很多博客上的文章,还有apollo也提供了两个作为例子的shell脚本,这样封装起来就方便一些,目前的情况是:
1、apollo公共API目前只支持建集群、namespace、item配置,不支持建应用app,因此APP应用需要自己提前创建;
2、要调用公共api还需要建立一个第三方的应用,生成对应的公共API token,然后还需要通过token对需要进行创建发布的配置对应应用进行授权。
以上工作都完成或者是通过SQL脚本前期都完成后,就可以开始脚本的工作了
1、是先定配置,我定的配置模板如下:
他是一个ini格式的配置文件
[h-common.TEST1.XXXDB.init]
name=XXXDB
appId=h-common
format=properties
isPublic=true
comment=这是一个说明
dataChangeCreatedBy=apollo
[h-common.MYAPPLICATION.init]
name=MYAPPLICATION
appId=h-common
format=properties
isPublic=false
comment=这是一个说明2
dataChangeCreatedBy=apollo
[h-common.TEST1.XXXDB.properties]
eureka.client.service-url.defaultZone=http://eureka:eurekaps@127.0.0.1:8070/eureka/
magic.oauth.login.encrypt=magic.oauth,login.encrypt
magic.graylog.opened=true
magic.qraylog.ip=127.0.0.1
[h-common.MYAPPLICATION.properties]
server.port=9090
spring.application.name=upmsName
注:
-
以.init]结尾的section节点部分是创建apollo中namespace所需要的参数,相当于是在项目中新建一个空的配置文件,而isPublic属性为true则为所有应用可以关联的公共配置,需要加上apollo中的部门前缀(默认为TEST1),为false则为当前应用私有的配置,不需要加部门前缀
-
以.properties]结尾的section节点部分是创建namespace下的item配置,和项目中properties格式文件内容一致
-
.ini配置文件中配置内容前后和"="中间都不要有多余的空格
-
.ini配置文件中目前配置内容格式固定为properties,不支持xml、yml、yaml、json、txt等
2、shell脚本
在执行目录下会有两个脚本:
-
openapi.sh
-
namespaceItemExecute.sh
openapi.sh是apollo自带:
#!/bin/bash
#
# Copyright 2023 Apollo Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# title openapi.sh
# description functions to call openapi through http
# author wxq
# date 2021-09-12
# Chinese reference website https://www.apolloconfig.com/#/zh/usage/apollo-open-api-platform
# English reference website https://www.apolloconfig.com/#/en/usage/apollo-open-api-platform
####################################### Global variables #######################################
# portal's address, just support 1 address without suffix '/'
# Don't use http://ip:port/ with suffix '/' or multiple address http://ip1:port1,http://ip2:port2
APOLLO_PORTAL_ADDRESS=${APOLLO_PORTAL_ADDRESS:-http://ip:port}
APOLLO_OPENAPI_TOKEN=${APOLLO_OPENAPI_TOKEN:-please_change_me_by_environment_variable}
CURL_OPTIONS=${CURL_OPTIONS:-}
echo "apollo portal address: ${APOLLO_PORTAL_ADDRESS}"
echo "curl options: ${CURL_OPTIONS}"
####################################### end of Global variables #######################################
####################################### basic http call #######################################
#######################################
# Http get by curl.
# Globals:
# APOLLO_PORTAL_ADDRESS: portal's address
# APOLLO_OPENAPI_TOKEN: openapi's token
# CURL_OPTIONS: options in curl
# Arguments:
# url_suffix
#######################################
function openapi_get() {
local url_suffix=$1
local url="${APOLLO_PORTAL_ADDRESS}/${url_suffix}"
curl ${CURL_OPTIONS} --header "Authorization: ${APOLLO_OPENAPI_TOKEN}" --header "Content-Type: application/json;charset=UTF-8" "${url}"
}
#######################################
# Http post by curl.
# Globals:
# APOLLO_PORTAL_ADDRESS: portal's address
# APOLLO_OPENAPI_TOKEN: openapi's token
# CURL_OPTIONS: options in curl
# Arguments:
# url_suffix
# body
#######################################
function openapi_post() {
local url_suffix=$1
local body=$2
local url="${APOLLO_PORTAL_ADDRESS}/${url_suffix}"
curl ${CURL_OPTIONS} --header "Authorization: ${APOLLO_OPENAPI_TOKEN}" --header "Content-Type: application/json;charset=UTF-8" --data "${body}" "${url}"
}
#######################################
# Http put by curl.
# Globals:
# APOLLO_PORTAL_ADDRESS: portal's address
# APOLLO_OPENAPI_TOKEN: openapi's token
# CURL_OPTIONS: options in curl
# Arguments:
# url_suffix
# body
#######################################
function openapi_put() {
local url_suffix=$1
local body=$2
local url="${APOLLO_PORTAL_ADDRESS}/${url_suffix}"
curl -g ${CURL_OPTIONS} --header "Authorization: ${APOLLO_OPENAPI_TOKEN}" --header "Content-Type: application/json;charset=UTF-8" -X PUT --data "${body}" "${url}"
}
#######################################
# Http delete by curl.
# Globals:
# APOLLO_PORTAL_ADDRESS: portal's address
# APOLLO_OPENAPI_TOKEN: openapi's token
# CURL_OPTIONS: options in curl
# Arguments:
# url_suffix
# body
#######################################
function openapi_delete() {
local url_suffix=$1
local body=$2
local url="${APOLLO_PORTAL_ADDRESS}/${url_suffix}"
curl ${CURL_OPTIONS} --header "Authorization: ${APOLLO_OPENAPI_TOKEN}" --header "Content-Type: application/json;charset=UTF-8" -X DELETE --data "${body}" "${url}"
}
####################################### end of basic http call #######################################
####################################### cluster #######################################
#######################################
# Get cluster.
# 获取集群
# Arguments:
# env
# appId
# clusterName
#######################################
function cluster_get() {
local env=$1
local appId=$2
local clusterName=$3
openapi_get "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}"
}
#######################################
# Create cluster in app's environment.
# 创建集群
# Arguments:
# env
# appId
# clusterName
# dataChangeCreatedBy
#######################################
function cluster_create() {
local env=$1
local appId=$2
local clusterName=$3
local dataChangeCreatedBy=$4
openapi_post "openapi/v1/envs/${env}/apps/${appId}/clusters" "$(cat <<BODY
{
"name":"${clusterName}",
"appId":"${appId}",
"dataChangeCreatedBy":"${dataChangeCreatedBy}"
}
BODY
)"
}
####################################### end of cluster #######################################
####################################### namespace #######################################
#######################################
# Create a namespace of a app.
# 创建namespace
# Arguments:
# appId
# name
# format
# isPublic
# comment
# dataChangeCreatedBy
#######################################
function namespace_create() {
local appId=$1
local name=$2
local format=$3
local isPublic=$4
local comment=$5
local dataChangeCreatedBy=$6
openapi_post "openapi/v1/apps/${appId}/appnamespaces" "$(cat <<BODY
{
"name": "${name}",
"appId": "${appId}",
"format": "${format}",
"isPublic": ${isPublic},
"comment": "${comment}",
"dataChangeCreatedBy": "${dataChangeCreatedBy}"
}
BODY
)"
}
#######################################
# Release a namespace.
# 发布配置
# Arguments:
# env
# appId
# clusterName
# namespaceName
# releaseTitle
# releaseComment
# releasedBy
#######################################
function namespace_release() {
local env=$1
local appId=$2
local clusterName=$3
local namespaceName=$4
local releaseTitle=$5
local releaseComment=$6
local releasedBy=$7
openapi_post "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}/namespaces/${namespaceName}/releases" "$(cat <<BODY
{
"releaseTitle":"${releaseTitle}",
"releaseComment":"${releaseComment}",
"releasedBy":"${releasedBy}"
}
BODY
)"
}
####################################### end of namespace #######################################
####################################### item #######################################
#######################################
# Create an item of a namespace.
# 新增配置
# Arguments:
# env
# appId
# clusterName
# namespaceName
# key
# value
# comment
# dataChangeCreatedBy
#######################################
function item_create() {
local env=$1
local appId=$2
local clusterName=$3
local namespaceName=$4
local key=$5
local value=$6
local comment=$7
local dataChangeCreatedBy=$8
openapi_post "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}/namespaces/${namespaceName}/items" "$(cat <<BODY
{
"key":"${key}",
"value":"${value}",
"comment":"${comment}",
"dataChangeCreatedBy":"${dataChangeCreatedBy}"
}
BODY
)"
}
#######################################
# Update an item of a namespace.
# 修改配置
# Arguments:
# env
# appId
# clusterName
# namespaceName
# key
# value
# comment
# dataChangeLastModifiedBy
#######################################
function item_update() {
local env=$1
local appId=$2
local clusterName=$3
local namespaceName=$4
local key=$5
local value=$6
local comment=$7
local dataChangeLastModifiedBy=$8
openapi_put "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}/namespaces/${namespaceName}/items/${key}" "$(cat <<BODY
{
"key":"${key}",
"value":"${value}",
"comment":"${comment}",
"dataChangeLastModifiedBy":"${dataChangeLastModifiedBy}"
}
BODY
)"
}
#######################################
# Update an item of a namespace, if item doesn's exist, create it.
# 修改配置,当配置不存在时自动创建
# Arguments:
# env
# appId
# clusterName
# namespaceName
# key
# value
# comment
# dataChangeLastModifiedBy
#######################################
function item_update_create_if_not_exists() {
local env=$1
local appId=$2
local clusterName=$3
local namespaceName=$4
local key=$5
local value=$6
local comment=$7
local dataChangeLastModifiedBy=$8
local dataChangeCreatedBy=$9
openapi_put "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}/namespaces/${namespaceName}/items/${key}?createIfNotExists=true" "$(cat <<BODY
{
"key":"${key}",
"value":"${value}",
"comment":"${comment}",
"dataChangeLastModifiedBy":"${dataChangeLastModifiedBy}",
"dataChangeCreatedBy":"${dataChangeCreatedBy}"
}
BODY
)"
}
#######################################
# Delete an item of a namespace.
# 删除配置
# Arguments:
# env
# appId
# clusterName
# namespaceName
# key
# operator
#######################################
function item_delete() {
local env=$1
local appId=$2
local clusterName=$3
local namespaceName=$4
local key=$5
local operator=$6
openapi_delete "openapi/v1/envs/${env}/apps/${appId}/clusters/${clusterName}/namespaces/${namespaceName}/items/${key}?operator=${operator}"
}
####################################### end of item #######################################
namespaceItemExecute.sh是自己写的,有些地方还不是很完善,参考了很多博客资料,忘记地址了,后面如果找到会提供
#!/bin/bash
file_path_ori=$1
# export global varialbes
export APOLLO_PORTAL_ADDRESS=$2
export APOLLO_OPENAPI_TOKEN=$3
export CURL_OPTIONS=""
# load functions
# 获取当前执行文件的绝对路径
current_file=$(readlink -f "$0")
absolute_path=$(dirname "$current_file")
source $absolute_path"/openapi.sh"
# 新增namespace方法
namespace_init() {
file_path=$1
echo $file_path "namespace_init"
printf "\n\n"
name=
appId=
format=
isPublic=
comment="create by openapi, bash scripts for release"
dataChangeCreatedBy='apollo'
# 先匹配以.init]结尾的节点
namespace_section_list=$(awk '/\.init\]$/' ${file_path} | sed 's/\[//g;s/\]//g')
for namespace_section in $namespace_section_list; do
# 匹配以.init]结尾的节点 条件1:去除第一行 条件2:去除空行 条件3:去除其他section的内容
namespace_list=$(awk -F ',' "/\[${namespace_section}\]$/{a=1}a==1" ${file_path}|sed -e '1d' -e '/^$/d' -e '/^\[.*\]/,$d')
# 遍历每一行,将每一行的内容按等号分割,并处理键值对
for line in $namespace_list; do
#key=$(echo $line | cut -d '=' -f 1)
key=$(echo $line|cut -d '=' -f -1)
#value=$(echo $line | cut -d '=' -f 2)
value=$(echo ${line#*=})
if [ "$key" = 'name' ]; then
name=$value
elif [ "$key" = 'appId' ]; then
appId=$value
elif [ "$key" = 'format' ]; then
format=$value
elif [ "$key" = 'isPublic' ]; then
isPublic=$value
elif [ "$key" = 'comment' ]; then
comment=$value
elif [ "$key" = 'dataChangeCreatedBy' ]; then
dataChangeCreatedBy=$value
fi
done
echo 'namespace create start----------------------'
echo $name "--->" $appId "--->" $format "--->" $comment "--->" $dataChangeCreatedBy
namespace_create ${appId} ${name} ${format} ${isPublic} ${comment} ${dataChangeCreatedBy}
echo 'namespace create end----------------------'
printf "\n\n"
echo 'namespace release start----------------------'
namespace_name=""
if [ "$isPublic" = 'true' ]; then
namespace_name="TEST1."$name
else
namespace_name=$name
fi
release "dev" $appId "default" $namespace_name
echo 'namespace release end----------------------'
done
}
# 新增item方法
item_init() {
file_path=$1
echo $file_path "item_init"
printf "\n\n"
key=
value=
comment="openapi-create-item"
dataChangeCreatedBy='apollo'
# 先匹配以.properties]结尾的节点
namespace_section_list=$(awk '/\.properties\]$/' ${file_path} | sed 's/\[//g;s/\]//g')
for namespace_section in $namespace_section_list; do
namespace=""
# namespace_section 进行切分,通过.进行切分
appId=$(echo $namespace_section | cut -d '.' -f 1)
second_name=$(echo $namespace_section | cut -d '.' -f 2)
third_name=$(echo $namespace_section | cut -d '.' -f 3)
forth_name=$(echo $namespace_section | cut -d '.' -f 4)
if [ "$forth_name" = 'properties' ]; then
namespace=$second_name'.'$third_name
else
namespace=$second_name
fi
# 匹配以.properties]结尾的节点 条件1:去除第一行 条件2:去除空行 条件3:去除其他section的内容
namespace_list=$(awk -F ',' "/\[${namespace_section}\]$/{a=1}a==1" ${file_path}|sed -e '1d' -e '/^$/d' -e '/^\[.*\]/,$d')
# 遍历每一行,将每一行的内容按等号分割,并处理键值对
for line in $namespace_list; do
# key=$(echo $line | cut -d '=' -f 1)
# value=$(echo $line | cut -d '=' -f 2)
key=$(echo $line|cut -d '=' -f -1)
value=$(echo ${line#*=})
echo "line="$line
# $(echo ${line#*=}) 这个方法如何等于后面是*可能会查找文件
if [[ $value =~ $file_path ]]
then
value=$(echo $line|cut -d '=' -f 2)
echo '包含'"$file_path""$(echo $line|cut -d '=' -f 2)"
echo $value
else
value=$value
echo '不包含'"$file_path"
fi
echo "item---->" $key "--->" $value "--->" $comment "--->" $dataChangeCreatedBy
# item_create "dev" ${appId} "default" ${namespace} ${key} ${value} ${comment} ${dataChangeCreatedBy}
echo 'item_update_create_if_not_exists start-----------------------------'
item_update_create_if_not_exists "dev" ${appId} default ${namespace} ${key} "$value" ${comment} ${dataChangeCreatedBy} ${dataChangeCreatedBy}
echo 'item_update_create_if_not_exists end-----------------------------'
done
# 每一次完成namespace中item的全部新增,则进行发布
echo 'item release start-----------------------------'
release "dev" $appId "default" $namespace
echo 'item release end-----------------------------'
done
}
# 发布方法
release() {
env_release=$1
app_id=$2
cluster_name=$3
namespace=$4
# 获取当前时间戳
releaseTitle=$(date +"%Y%m%d%H%M%S%N")"-version"
releaseComment="公共api发布namespace"
releasedBy="apollo"
echo "release----->env_release:" $env_release "----->app_id:" $app_id "----->cluster_name:" $cluster_name "----->namespace:" $namespace
echo "release----->releaseTitle:" $releaseTitle "----->releaseComment:" $releaseComment "----->releasedBy:" $releasedBy
namespace_release ${env_release} ${app_id} ${cluster_name} ${namespace} ${releaseTitle} ${releaseComment} ${releasedBy}
printf "\n\n"
}
echo $file_path_ori "--------start-------"
printf "\n"
namespace_init $file_path_ori
item_init $file_path_ori
openapi.sh为接收参数通过curl请求apollo公共API调用,namespaceItemExecute.sh为解析对应xxx.ini配置文件,然后封装对应参数调用openapi.sh脚本中相关方法执行对应操作,执行例子如下:
sh namespaceItemExecute.sh namespace.ini http://127.0.0.1:8070 token
这个脚本需要传入三个参数:
-
需要解析的.ini配置文件路径和文件名,如:namespace.ini
-
apollo中portal服务对应的请求请求地址,如:http://159.45.0.68:8070/mfapollo
-
apollo开放平台第三方应用对应token,如:ce86a34e63bf5ac1c35fc3ba8723sdgsaersdd035635ae45c0c83b0d28;对应的第三方应用创建、token生成、赋权等地址在Apollo
在执行这个脚本之前,需要先创建apollo中对应的应用,这个脚本目前只是提供了配置文件和对应配置内容的初始化操作
任何语言的第三方应用都可以调用Apollo的Open API,在调用接口时,需要设置注意以下两点:
-
Http Header中增加一个Authorization字段,字段值为申请的token
-
Http Header的Content-Type字段需要设置成application/json;charset=UTF-8
参考地址:
Shell script - Linux下解析ini配置文件 - 简书
linux Shell 读取和写入ini配置文件 (脚本实现读取和写入,附使用方法和讲解)_sh如何往ini添加数据-CSDN博客