blob: 1190d6b55651f39caa9a1b440aa4172f9de9fb03 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package client
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/apache/servicecomb-kie/pkg/common"
"net/http"
"net/url"
"strings"
"github.com/apache/servicecomb-kie/pkg/model"
"github.com/go-chassis/foundation/httpclient"
"github.com/go-chassis/foundation/security"
"github.com/go-chassis/go-chassis/pkg/util/httputil"
"github.com/go-mesh/openlogging"
)
//const
const (
version = "v1"
APIPathKV = "kie/kv"
)
//client errors
var (
ErrKeyNotExist = errors.New("can not find value")
)
//Client is the servicecomb kie rest client.
//it is concurrency safe
type Client struct {
opts Config
cipher security.Cipher
c *httpclient.URLClient
}
//Config is the config of client
type Config struct {
Endpoint string
DefaultLabels map[string]string
VerifyPeer bool //TODO make it works, now just keep it false
}
//New create a client
func New(config Config) (*Client, error) {
u, err := url.Parse(config.Endpoint)
if err != nil {
return nil, err
}
httpOpts := &httpclient.URLClientOption{}
if u.Scheme == "https" {
httpOpts.TLSConfig = &tls.Config{
InsecureSkipVerify: !config.VerifyPeer,
}
}
c, err := httpclient.GetURLClient(httpOpts)
if err != nil {
return nil, err
}
return &Client{
opts: config,
c: c,
}, nil
}
//Put create value of a key
func (c *Client) Put(ctx context.Context, kv model.KVDoc, opts ...OpOption) (*model.KVDoc, error) {
options := OpOptions{}
for _, o := range opts {
o(&options)
}
if options.Project == "" {
options.Project = defaultProject
}
url := fmt.Sprintf("%s/%s/%s/%s/%s", c.opts.Endpoint, version, options.Project, APIPathKV, kv.Key)
h := http.Header{}
h.Set("Content-Type", "application/json")
body, _ := json.Marshal(kv)
resp, err := c.c.HTTPDoWithContext(ctx, "PUT", url, h, body)
if err != nil {
return nil, err
}
b := httputil.ReadBody(resp)
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, ErrKeyNotExist
}
openlogging.Error("get failed", openlogging.WithTags(openlogging.Tags{
"k": kv.Key,
"status": resp.Status,
"body": b,
}))
return nil, fmt.Errorf("get %s failed,http status [%s], body [%s]", kv.Key, resp.Status, b)
}
kvs := &model.KVDoc{}
err = json.Unmarshal(b, kvs)
if err != nil {
openlogging.Error("unmarshal kv failed:" + err.Error())
return nil, err
}
return kvs, nil
}
//Get get value of a key
func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*model.KVResponse, error) {
options := GetOptions{}
for _, o := range opts {
o(&options)
}
if options.Project == "" {
options.Project = defaultProject
}
url := fmt.Sprintf("%s/%s/%s/%s/%s", c.opts.Endpoint, version, options.Project, APIPathKV, key)
h := http.Header{}
resp, err := c.c.HTTPDoWithContext(ctx, "GET", url, h, nil)
if err != nil {
return nil, err
}
b := httputil.ReadBody(resp)
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, ErrKeyNotExist
}
openlogging.Error("get failed", openlogging.WithTags(openlogging.Tags{
"k": key,
"status": resp.Status,
"body": b,
}))
return nil, fmt.Errorf("get %s failed,http status [%s], body [%s]", key, resp.Status, b)
}
var kvs []*model.KVResponse
err = json.Unmarshal(b, &kvs)
if err != nil {
openlogging.Error("unmarshal kv failed:" + err.Error())
return nil, err
}
return kvs, nil
}
//SearchByLabels get value by lables
func (c *Client) SearchByLabels(ctx context.Context, opts ...GetOption) ([]*model.KVResponse, error) {
options := GetOptions{}
for _, o := range opts {
o(&options)
}
if options.Project == "" {
options.Project = defaultProject
}
lableReq := ""
for _, labels := range options.Labels {
lableReq += common.QueryParamQ + "="
for labelKey, labelValue := range labels {
lableReq += labelKey + ":" + labelValue + "+"
}
if labels != nil && len(labels) > 0 {
lableReq = strings.TrimRight(lableReq, "+")
}
lableReq += common.QueryByLabelsCon
}
if options.Labels != nil && len(options.Labels) > 0 {
lableReq = strings.TrimRight(lableReq, common.QueryByLabelsCon)
}
url := fmt.Sprintf("%s/%s/%s/%s?%s", c.opts.Endpoint, version, options.Project, APIPathKV, lableReq)
h := http.Header{}
resp, err := c.c.HTTPDoWithContext(ctx, "GET", url, h, nil)
if err != nil {
return nil, err
}
b := httputil.ReadBody(resp)
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, ErrKeyNotExist
}
openlogging.Error("get failed", openlogging.WithTags(openlogging.Tags{
"p": options.Project,
"status": resp.Status,
"body": b,
}))
return nil, fmt.Errorf("search %s failed,http status [%s], body [%s]", lableReq, resp.Status, b)
}
var kvs []*model.KVResponse
err = json.Unmarshal(b, &kvs)
if err != nil {
openlogging.Error("unmarshal kv failed:" + err.Error())
return nil, err
}
return kvs, nil
}
//Delete remove kv
func (c *Client) Delete(ctx context.Context, kvID, labelID string, opts ...OpOption) error {
options := OpOptions{}
for _, o := range opts {
o(&options)
}
if options.Project == "" {
options.Project = defaultProject
}
url := fmt.Sprintf("%s/%s/%s/%s/?kvID=%s", c.opts.Endpoint, version, options.Project, APIPathKV, kvID)
if labelID != "" {
url = fmt.Sprintf("%s?labelID=%s", url, labelID)
}
h := http.Header{}
h.Set("Content-Type", "application/json")
resp, err := c.c.HTTPDoWithContext(ctx, "DELETE", url, h, nil)
if err != nil {
return err
}
b := httputil.ReadBody(resp)
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("delete %s failed,http status [%s], body [%s]", kvID, resp.Status, b)
}
return nil
}