Skip to content

Commit

Permalink
1. 支持{kev:val}和{kev:{val:xxx,bscp_kv_type:xxx}}导入
Browse files Browse the repository at this point in the history
2. 支持json和yaml导入导出
  • Loading branch information
Ambition9186 committed Jan 19, 2024
1 parent f3e4e5e commit 5ed0b27
Show file tree
Hide file tree
Showing 6 changed files with 563 additions and 0 deletions.
293 changes: 293 additions & 0 deletions bcs-services/bcs-bscp/cmd/api-server/service/kv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/*
* Tencent is pleased to support the open source community by making Blueking Container Service available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* 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.
*/

package service

import (
"bufio"
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"strconv"
"strings"

"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/xuri/excelize/v2"
"gopkg.in/yaml.v3"

"github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/dal/table"
"github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/iam/auth"
"github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/kit"
pbcs "github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/protocol/config-server"
"github.com/TencentBlueKing/bk-bcs/bcs-services/bcs-bscp/pkg/rest"
)

type kvService struct {
authorizer auth.Authorizer
cfgClient pbcs.ConfigClient
}

func newKvService(authorizer auth.Authorizer,
cfgClient pbcs.ConfigClient) *kvService {
s := &kvService{
authorizer: authorizer,
cfgClient: cfgClient,
}
return s
}

// Import is used to handle file import requests.
func (m *kvService) Import(w http.ResponseWriter, r *http.Request) {

kt := kit.MustGetKit(r.Context())

appIdStr := chi.URLParam(r, "app_id")
appId, _ := strconv.Atoi(appIdStr)
if appId == 0 {
_ = render.Render(w, r, rest.BadRequest(errors.New("validation parameter fail")))
return
}
reader := bufio.NewReader(r.Body)

bytes, err := io.ReadAll(reader)
if err != nil {
_ = render.Render(w, r, rest.BadRequest(err))
return
}

var kvMap map[string]interface{}
switch {
case readJSONFile(bytes, &kvMap):
case readYAMLFile(bytes, &kvMap):
default:
_ = render.Render(w, r, rest.BadRequest(errors.New("unsupported file type")))
return
}

req := &pbcs.BatchUpsertKvsReq{
BizId: kt.BizID,
AppId: uint32(appId),
Kvs: handleKv(kvMap),
}

resp, err := m.cfgClient.BatchUpsertKvs(kt.RpcCtx(), req)
if err != nil {
_ = render.Render(w, r, rest.BadRequest(err))
return
}

_ = render.Render(w, r, rest.OKRender(resp))
}

// yaml格式时 类型是map[interface{}]interface{}
// json格式时 类型是map[string]interface{}
func handleKv(result map[string]interface{}) []*pbcs.BatchUpsertKvsReq_Kv {
kvMap := []*pbcs.BatchUpsertKvsReq_Kv{}
for key, value := range result {
KVType := ""
switch entry := value.(type) {
case map[interface{}]interface{}:
kvType, okType := entry["bscp_kv_type"].(string)
kvValue, okVal := entry["value"]
if okType && okVal {
var val string
// 如果是json需要序列化以下
val = fmt.Sprintf("%v", kvValue)
if kvType == string(table.KvJson) {
mv, _ := json.Marshal(kvValue)
val = string(mv)
}
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: val,
KvType: kvType,
})
}
case map[string]interface{}:
kvType, okType := entry["bscp_kv_type"].(string)
kvValue, okVal := entry["value"]
if okType && okVal {
var val string
// 如果是json需要序列化以下
val = fmt.Sprintf("%v", kvValue)
if kvType == string(table.KvJson) {
mv, _ := json.Marshal(kvValue)
val = string(mv)
}
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: val,
KvType: kvType,
})
}
default:
// 判断是不是数值类型
if isNumber(value) {
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: fmt.Sprintf("%v", value),
KvType: string(table.KvNumber),
})
} else {
// json格式单独处理下
var js map[string]interface{}
err := json.Unmarshal([]byte(value.(string)), &js)
if err == nil {
// 长度等于2需单独处理下
if len(js) == 2 {
kvType, okType := js["bscp_kv_type"].(string)
kvValue, okVal := js["value"]
if okType && okVal {
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: fmt.Sprintf("%v", kvValue),
KvType: kvType,
})
}
} else {
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: fmt.Sprintf("%v", value),
KvType: string(table.KvJson),
})
}
} else {
KVType = determineType(value.(string))
kvMap = append(kvMap, &pbcs.BatchUpsertKvsReq_Kv{
Key: key,
Value: fmt.Sprintf("%v", value),
KvType: KVType,
})
}
}
}
}
return kvMap
}

// 读取json文件
func readJSONFile(bytes []byte, result *map[string]interface{}) bool {
return !json.Valid(bytes) && json.Unmarshal(bytes, &result) == nil
}

// 读取yaml文件
func readYAMLFile(bytes []byte, result *map[string]interface{}) bool {
return yaml.Unmarshal(bytes, &result) == nil
}

// 根据值判断类型
func determineType(value string) string {
var result string
switch {
case isYAML(value):
result = "yaml"
case isXML(value):
result = "xml"
case isTEXT(value):
result = "text"
case isNumber(value):
result = "number"
default:
result = "string"
}
return result
}

// 判断是否为 JSON
func isJSON(value string) bool {
var js map[string]interface{}
return json.Unmarshal([]byte(value), &js) == nil
}

// 判断是否为 YAML
func isYAML(value string) bool {
var yml map[string]interface{}
return yaml.Unmarshal([]byte(value), &yml) == nil
}

// 判断是否为 XML
func isXML(value string) bool {
var xmlData interface{}
return xml.Unmarshal([]byte(value), &xmlData) == nil
}

// 判断是否为 XML
func isTEXT(value string) bool {
return strings.Contains(value, "\n")
}

// 判断是不是 Number
func isNumber(value interface{}) bool {
// 获取值的类型
valType := reflect.TypeOf(value)

// 检查类型是否为数字
switch valType.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return true
case reflect.Float32, reflect.Float64:
return true
default:
return false
}
}

func parseExcelFile(file io.Reader) ([]*pbcs.BatchUpsertKvsReq_Kv, error) {

r := bufio.NewReader(file)
f, err := excelize.OpenReader(r)
if err != nil {
return nil, err
}

rows, err := f.GetRows("Sheet1")
if err != nil {
return nil, err
}

var kvs []*pbcs.BatchUpsertKvsReq_Kv
for i := 1; i < len(rows); i++ {
kv := &pbcs.BatchUpsertKvsReq_Kv{
Key: rows[i][0],
Value: rows[i][1],
KvType: rows[i][2],
}
kvs = append(kvs, kv)
}

return kvs, nil
}

func peek(r *bufio.Reader, size int) ([]byte, error) {

peekedData, err := r.Peek(size)
if err != nil {
return nil, err
}
return peekedData, nil
}

func startsWith(r *bufio.Reader, prefix string) bool {
peekedData, err := peek(r, len(prefix))
if err != nil {
return false
}
return bytes.Equal(peekedData, []byte(prefix))
}
4 changes: 4 additions & 0 deletions bcs-services/bcs-bscp/cmd/api-server/service/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type proxy struct {
authorizer auth.Authorizer
cfgClient pbcs.ConfigClient
configImportService *configImport
kvService *kvService
}

// newProxy create new mux proxy.
Expand Down Expand Up @@ -81,6 +82,8 @@ func newProxy(dis serviced.Discover) (*proxy, error) {
return nil, err
}

kv := newKvService(authorizer, cfgClient)

p := &proxy{
cfgSvrMux: cfgSvrMux,
repo: repo,
Expand All @@ -89,6 +92,7 @@ func newProxy(dis serviced.Discover) (*proxy, error) {
authorizer: authorizer,
authSvrMux: authSvrMux,
cfgClient: cfgClient,
kvService: kv,
}

p.initBizsOfTmplSpaces()
Expand Down
Loading

0 comments on commit 5ed0b27

Please sign in to comment.