Compare commits

...

17 Commits

Author SHA1 Message Date
Sunrisea e868a001ec
feat: add default log rolling config , update version of nacos go client (#841)
* add default log rolling config , update version of nacos go client

* add v2.2.9 deprecated information
2025-08-18 15:09:40 +08:00
Sunrisea 8bad3fed0f
Update grpc version and go version (#839)
* Update Grpc and protobuf version

* fix version

* fix workflow

* fix codestyle

* fix go mod
2025-08-14 11:29:27 +08:00
Sunrisea 6d3e89ff89
fix: Supports multiple subscriptions to services across different clusters. (#838)
* Supports multiple subscriptions to services across different clusters.

* fix bug
2025-08-13 19:08:52 +08:00
shalk(xiao kun) ead2368c2f
fix: nacos client unsubscribe (#836) 2025-08-13 11:01:30 +08:00
Sunrisea eb3adf0f0a
Fix search config bug (#828) 2025-05-28 15:48:02 +08:00
Sunrisea 2bc352f538
Fix Nacos 3.0 Search Config bug (#825) 2025-05-18 16:03:35 +08:00
Sunrisea a0fc325190
Support Nacos 3.0 Search Config (#824) 2025-05-15 14:29:52 +08:00
Xin Luo 5758f57b6b
Fix: fix deadlock when close client multi times (#817)
* Fix: fix deadlock when close client multi times

* Fix: Rename isClose to isClosed
2025-05-15 14:26:31 +08:00
blake.qiu 310a82873f
fix: resubscribe to previously subscribed configurations after rpcClient reconnects to the server (#802)
fix: resubscribe to previously subscribed configurations after rpcClient reconnects to the server
2025-05-15 14:24:49 +08:00
blake.qiu f1545e0403 fix: since the program will only log in once, there is no way to refresh the token. 2025-05-15 14:23:20 +08:00
Sunrisea 741b6adca7
fix(#811): Remove error log when matched ramCredentialProvider not found 2025-05-13 20:40:33 +08:00
Sunrisea 98686b0b0a fix no matched provider log 2025-05-07 10:55:22 +08:00
Xin Luo 9df154169b
fix error request type name (#810) 2025-04-07 17:15:08 +08:00
zeyu-zhang 476a2c4fa0
Add support for customized credentials provider (#800)
Signed-off-by: Zeyu Zhang <zhangzeyu.zzy@alibaba-inc.com>
2025-03-18 16:40:43 +08:00
Sunrisea 3bbe1db11e
Refactor the client auth module and support more aliyun ram auth mode (#797)
* refactor client auth module, support more mode
2025-02-25 20:07:39 +08:00
Sunrisea c925bcf60d
support config_tags (#799) 2025-02-25 20:06:26 +08:00
Sunrisea 6f5bdc4cb6
Fix the bug of callback not triggered on client when config deleted (#796)
* fix get empty config bug

* fix test bug

* fix test bug
2025-02-12 17:19:03 +08:00
46 changed files with 2919 additions and 933 deletions

View File

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
config:
- go_version: 1.18
- go_version: 1.21
steps:
- name: Set up Go 1.x

View File

@ -1,467 +1,233 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* 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.
*/
//
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: nacos_grpc_service.proto
// versions:
// protoc-gen-go v1.36.7
// protoc v5.29.3
// source: api/proto/nacos_grpc_service.proto
package grpc
package auto
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
any "github.com/golang/protobuf/ptypes/any"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Metadata struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
ClientIp string `protobuf:"bytes,8,opt,name=clientIp,proto3" json:"clientIp,omitempty"`
Headers map[string]string `protobuf:"bytes,7,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Headers map[string]string `protobuf:"bytes,7,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Metadata) Reset() {
*x = Metadata{}
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Metadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (m *Metadata) Reset() { *m = Metadata{} }
func (m *Metadata) String() string { return proto.CompactTextString(m) }
func (*Metadata) ProtoMessage() {}
func (x *Metadata) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
func (*Metadata) Descriptor() ([]byte, []int) {
return fileDescriptor_f908b146bdb05ce9, []int{0}
return file_api_proto_nacos_grpc_service_proto_rawDescGZIP(), []int{0}
}
func (m *Metadata) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Metadata.Unmarshal(m, b)
}
func (m *Metadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Metadata.Marshal(b, m, deterministic)
}
func (m *Metadata) XXX_Merge(src proto.Message) {
xxx_messageInfo_Metadata.Merge(m, src)
}
func (m *Metadata) XXX_Size() int {
return xxx_messageInfo_Metadata.Size(m)
}
func (m *Metadata) XXX_DiscardUnknown() {
xxx_messageInfo_Metadata.DiscardUnknown(m)
}
var xxx_messageInfo_Metadata proto.InternalMessageInfo
func (m *Metadata) GetType() string {
if m != nil {
return m.Type
func (x *Metadata) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (m *Metadata) GetClientIp() string {
if m != nil {
return m.ClientIp
func (x *Metadata) GetClientIp() string {
if x != nil {
return x.ClientIp
}
return ""
}
func (m *Metadata) GetHeaders() map[string]string {
if m != nil {
return m.Headers
func (x *Metadata) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
type Payload struct {
state protoimpl.MessageState `protogen:"open.v1"`
Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"`
Body *any.Any `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Body *anypb.Any `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Payload) Reset() {
*x = Payload{}
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Payload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (m *Payload) Reset() { *m = Payload{} }
func (m *Payload) String() string { return proto.CompactTextString(m) }
func (*Payload) ProtoMessage() {}
func (x *Payload) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Payload.ProtoReflect.Descriptor instead.
func (*Payload) Descriptor() ([]byte, []int) {
return fileDescriptor_f908b146bdb05ce9, []int{1}
return file_api_proto_nacos_grpc_service_proto_rawDescGZIP(), []int{1}
}
func (m *Payload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Payload.Unmarshal(m, b)
}
func (m *Payload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Payload.Marshal(b, m, deterministic)
}
func (m *Payload) XXX_Merge(src proto.Message) {
xxx_messageInfo_Payload.Merge(m, src)
}
func (m *Payload) XXX_Size() int {
return xxx_messageInfo_Payload.Size(m)
}
func (m *Payload) XXX_DiscardUnknown() {
xxx_messageInfo_Payload.DiscardUnknown(m)
}
var xxx_messageInfo_Payload proto.InternalMessageInfo
func (m *Payload) GetMetadata() *Metadata {
if m != nil {
return m.Metadata
func (x *Payload) GetMetadata() *Metadata {
if x != nil {
return x.Metadata
}
return nil
}
func (m *Payload) GetBody() *any.Any {
if m != nil {
return m.Body
func (x *Payload) GetBody() *anypb.Any {
if x != nil {
return x.Body
}
return nil
}
func init() {
proto.RegisterType((*Metadata)(nil), "Metadata")
proto.RegisterMapType((map[string]string)(nil), "Metadata.HeadersEntry")
proto.RegisterType((*Payload)(nil), "Payload")
var File_api_proto_nacos_grpc_service_proto protoreflect.FileDescriptor
const file_api_proto_nacos_grpc_service_proto_rawDesc = "" +
"\n" +
"\"api/proto/nacos_grpc_service.proto\x1a\x19google/protobuf/any.proto\"\xa8\x01\n" +
"\bMetadata\x12\x12\n" +
"\x04type\x18\x03 \x01(\tR\x04type\x12\x1a\n" +
"\bclientIp\x18\b \x01(\tR\bclientIp\x120\n" +
"\aheaders\x18\a \x03(\v2\x16.Metadata.HeadersEntryR\aheaders\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Z\n" +
"\aPayload\x12%\n" +
"\bmetadata\x18\x02 \x01(\v2\t.MetadataR\bmetadata\x12(\n" +
"\x04body\x18\x03 \x01(\v2\x14.google.protobuf.AnyR\x04body28\n" +
"\rRequestStream\x12'\n" +
"\rrequestStream\x12\b.Payload\x1a\b.Payload\"\x000\x012*\n" +
"\aRequest\x12\x1f\n" +
"\arequest\x12\b.Payload\x1a\b.Payload\"\x002>\n" +
"\x0fBiRequestStream\x12+\n" +
"\x0frequestBiStream\x12\b.Payload\x1a\b.Payload\"\x00(\x010\x01B^\n" +
"\x1fcom.alibaba.nacos.api.grpc.autoP\x01Z9github.com/nacos-group/nacos-sdk-go/v2/api/grpc/auto;autob\x06proto3"
var (
file_api_proto_nacos_grpc_service_proto_rawDescOnce sync.Once
file_api_proto_nacos_grpc_service_proto_rawDescData []byte
)
func file_api_proto_nacos_grpc_service_proto_rawDescGZIP() []byte {
file_api_proto_nacos_grpc_service_proto_rawDescOnce.Do(func() {
file_api_proto_nacos_grpc_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_nacos_grpc_service_proto_rawDesc), len(file_api_proto_nacos_grpc_service_proto_rawDesc)))
})
return file_api_proto_nacos_grpc_service_proto_rawDescData
}
func init() { proto.RegisterFile("nacos_grpc_service.proto", fileDescriptor_f908b146bdb05ce9) }
var fileDescriptor_f908b146bdb05ce9 = []byte{
// 333 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0x4f, 0x4b, 0xeb, 0x40,
0x10, 0x7f, 0xdb, 0xf6, 0xbd, 0xa4, 0xd3, 0x57, 0x2a, 0x4b, 0x91, 0x98, 0x4b, 0x4b, 0x45, 0x0c,
0x0a, 0xdb, 0x12, 0x2f, 0xa5, 0x07, 0xc1, 0x82, 0xa0, 0x07, 0xa1, 0xc4, 0x9b, 0x97, 0x32, 0x49,
0xd6, 0x1a, 0x4c, 0xb3, 0x71, 0xb3, 0x29, 0xec, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x69, 0xb0, 0x20,
0xde, 0x66, 0x7e, 0x7f, 0xe6, 0xc7, 0xcc, 0x80, 0x93, 0x61, 0x24, 0x8a, 0xf5, 0x46, 0xe6, 0xd1,
0xba, 0xe0, 0x72, 0x97, 0x44, 0x9c, 0xe5, 0x52, 0x28, 0xe1, 0x9e, 0x6d, 0x84, 0xd8, 0xa4, 0x7c,
0x6a, 0xba, 0xb0, 0x7c, 0x9d, 0x62, 0xa6, 0x2b, 0x6a, 0xf2, 0x49, 0xc0, 0x7e, 0xe2, 0x0a, 0x63,
0x54, 0x48, 0x29, 0x74, 0x94, 0xce, 0xb9, 0xd3, 0x1e, 0x13, 0xaf, 0x1b, 0x98, 0x9a, 0xba, 0x60,
0x47, 0x69, 0xc2, 0x33, 0xf5, 0x98, 0x3b, 0xb6, 0xc1, 0x9b, 0x9e, 0xce, 0xc0, 0x7a, 0xe3, 0x18,
0x73, 0x59, 0x38, 0xd6, 0xb8, 0xed, 0xf5, 0xfc, 0x53, 0x76, 0x98, 0xc5, 0x1e, 0x2a, 0xe2, 0x3e,
0x53, 0x52, 0x07, 0x07, 0x99, 0xbb, 0x80, 0xff, 0xdf, 0x09, 0x7a, 0x02, 0xed, 0x77, 0xae, 0x1d,
0x62, 0x06, 0xef, 0x4b, 0x3a, 0x84, 0xbf, 0x3b, 0x4c, 0x4b, 0xee, 0xb4, 0x0c, 0x56, 0x35, 0x8b,
0xd6, 0x9c, 0x4c, 0x5e, 0xc0, 0x5a, 0xa1, 0x4e, 0x05, 0xc6, 0xf4, 0x02, 0xec, 0x6d, 0x1d, 0x64,
0x74, 0x3d, 0xbf, 0xdb, 0x24, 0x07, 0x0d, 0x45, 0x3d, 0xe8, 0x84, 0x22, 0xd6, 0x66, 0x9f, 0x9e,
0x3f, 0x64, 0xd5, 0x19, 0xd8, 0xe1, 0x0c, 0xec, 0x2e, 0xd3, 0x81, 0x51, 0xf8, 0x73, 0xe8, 0x07,
0xfc, 0xa3, 0xe4, 0x85, 0x7a, 0x56, 0x92, 0xe3, 0x96, 0x5e, 0x42, 0x5f, 0x1e, 0x01, 0x36, 0xab,
0xc3, 0xdd, 0xa6, 0x9a, 0xfc, 0x99, 0x11, 0xff, 0x0a, 0xac, 0xda, 0x49, 0x47, 0x60, 0xd5, 0x9e,
0x9f, 0xd5, 0xfe, 0x2d, 0x0c, 0x96, 0xc9, 0x71, 0xce, 0x35, 0x0c, 0x6a, 0xcf, 0x32, 0xf9, 0x2d,
0xc9, 0x23, 0x33, 0xb2, 0x3c, 0x87, 0x51, 0x24, 0xb6, 0x0c, 0xd3, 0x24, 0xc4, 0x10, 0x99, 0xf9,
0x37, 0xc3, 0x3c, 0x61, 0xfb, 0x9f, 0x33, 0x2c, 0x95, 0x58, 0x91, 0xf0, 0x9f, 0x59, 0xef, 0xe6,
0x2b, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x9e, 0xc7, 0x2d, 0x0f, 0x02, 0x00, 0x00,
var file_api_proto_nacos_grpc_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_api_proto_nacos_grpc_service_proto_goTypes = []any{
(*Metadata)(nil), // 0: Metadata
(*Payload)(nil), // 1: Payload
nil, // 2: Metadata.HeadersEntry
(*anypb.Any)(nil), // 3: google.protobuf.Any
}
var file_api_proto_nacos_grpc_service_proto_depIdxs = []int32{
2, // 0: Metadata.headers:type_name -> Metadata.HeadersEntry
0, // 1: Payload.metadata:type_name -> Metadata
3, // 2: Payload.body:type_name -> google.protobuf.Any
1, // 3: RequestStream.requestStream:input_type -> Payload
1, // 4: Request.request:input_type -> Payload
1, // 5: BiRequestStream.requestBiStream:input_type -> Payload
1, // 6: RequestStream.requestStream:output_type -> Payload
1, // 7: Request.request:output_type -> Payload
1, // 8: BiRequestStream.requestBiStream:output_type -> Payload
6, // [6:9] is the sub-list for method output_type
3, // [3:6] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// RequestStreamClient is the client API for RequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RequestStreamClient interface {
// build a streamRequest
RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (RequestStream_RequestStreamClient, error)
func init() { file_api_proto_nacos_grpc_service_proto_init() }
func file_api_proto_nacos_grpc_service_proto_init() {
if File_api_proto_nacos_grpc_service_proto != nil {
return
}
type requestStreamClient struct {
cc *grpc.ClientConn
}
func NewRequestStreamClient(cc *grpc.ClientConn) RequestStreamClient {
return &requestStreamClient{cc}
}
func (c *requestStreamClient) RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (RequestStream_RequestStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_RequestStream_serviceDesc.Streams[0], "/RequestStream/requestStream", opts...)
if err != nil {
return nil, err
}
x := &requestStreamRequestStreamClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type RequestStream_RequestStreamClient interface {
Recv() (*Payload, error)
grpc.ClientStream
}
type requestStreamRequestStreamClient struct {
grpc.ClientStream
}
func (x *requestStreamRequestStreamClient) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// RequestStreamServer is the server API for RequestStream service.
type RequestStreamServer interface {
// build a streamRequest
RequestStream(*Payload, RequestStream_RequestStreamServer) error
}
// UnimplementedRequestStreamServer can be embedded to have forward compatible implementations.
type UnimplementedRequestStreamServer struct {
}
func (*UnimplementedRequestStreamServer) RequestStream(req *Payload, srv RequestStream_RequestStreamServer) error {
return status.Errorf(codes.Unimplemented, "method RequestStream not implemented")
}
func RegisterRequestStreamServer(s *grpc.Server, srv RequestStreamServer) {
s.RegisterService(&_RequestStream_serviceDesc, srv)
}
func _RequestStream_RequestStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Payload)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RequestStreamServer).RequestStream(m, &requestStreamRequestStreamServer{stream})
}
type RequestStream_RequestStreamServer interface {
Send(*Payload) error
grpc.ServerStream
}
type requestStreamRequestStreamServer struct {
grpc.ServerStream
}
func (x *requestStreamRequestStreamServer) Send(m *Payload) error {
return x.ServerStream.SendMsg(m)
}
var _RequestStream_serviceDesc = grpc.ServiceDesc{
ServiceName: "RequestStream",
HandlerType: (*RequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestStream",
Handler: _RequestStream_RequestStream_Handler,
ServerStreams: true,
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_nacos_grpc_service_proto_rawDesc), len(file_api_proto_nacos_grpc_service_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 3,
},
},
Metadata: "nacos_grpc_service.proto",
}
// RequestClient is the client API for Request service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RequestClient interface {
// Sends a commonRequest
Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error)
}
type requestClient struct {
cc *grpc.ClientConn
}
func NewRequestClient(cc *grpc.ClientConn) RequestClient {
return &requestClient{cc}
}
func (c *requestClient) Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error) {
out := new(Payload)
err := c.cc.Invoke(ctx, "/Request/request", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RequestServer is the server API for Request service.
type RequestServer interface {
// Sends a commonRequest
Request(context.Context, *Payload) (*Payload, error)
}
// UnimplementedRequestServer can be embedded to have forward compatible implementations.
type UnimplementedRequestServer struct {
}
func (*UnimplementedRequestServer) Request(ctx context.Context, req *Payload) (*Payload, error) {
return nil, status.Errorf(codes.Unimplemented, "method Request not implemented")
}
func RegisterRequestServer(s *grpc.Server, srv RequestServer) {
s.RegisterService(&_Request_serviceDesc, srv)
}
func _Request_Request_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Payload)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RequestServer).Request(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/Request/Request",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RequestServer).Request(ctx, req.(*Payload))
}
return interceptor(ctx, in, info, handler)
}
var _Request_serviceDesc = grpc.ServiceDesc{
ServiceName: "Request",
HandlerType: (*RequestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "request",
Handler: _Request_Request_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "nacos_grpc_service.proto",
}
// BiRequestStreamClient is the client API for BiRequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BiRequestStreamClient interface {
// Sends a commonRequest
RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (BiRequestStream_RequestBiStreamClient, error)
}
type biRequestStreamClient struct {
cc *grpc.ClientConn
}
func NewBiRequestStreamClient(cc *grpc.ClientConn) BiRequestStreamClient {
return &biRequestStreamClient{cc}
}
func (c *biRequestStreamClient) RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (BiRequestStream_RequestBiStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_BiRequestStream_serviceDesc.Streams[0], "/BiRequestStream/requestBiStream", opts...)
if err != nil {
return nil, err
}
x := &biRequestStreamRequestBiStreamClient{stream}
return x, nil
}
type BiRequestStream_RequestBiStreamClient interface {
Send(*Payload) error
Recv() (*Payload, error)
grpc.ClientStream
}
type biRequestStreamRequestBiStreamClient struct {
grpc.ClientStream
}
func (x *biRequestStreamRequestBiStreamClient) Send(m *Payload) error {
return x.ClientStream.SendMsg(m)
}
func (x *biRequestStreamRequestBiStreamClient) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// BiRequestStreamServer is the server API for BiRequestStream service.
type BiRequestStreamServer interface {
// Sends a commonRequest
RequestBiStream(BiRequestStream_RequestBiStreamServer) error
}
// UnimplementedBiRequestStreamServer can be embedded to have forward compatible implementations.
type UnimplementedBiRequestStreamServer struct {
}
func (*UnimplementedBiRequestStreamServer) RequestBiStream(srv BiRequestStream_RequestBiStreamServer) error {
return status.Errorf(codes.Unimplemented, "method RequestBiStream not implemented")
}
func RegisterBiRequestStreamServer(s *grpc.Server, srv BiRequestStreamServer) {
s.RegisterService(&_BiRequestStream_serviceDesc, srv)
}
func _BiRequestStream_RequestBiStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BiRequestStreamServer).RequestBiStream(&biRequestStreamRequestBiStreamServer{stream})
}
type BiRequestStream_RequestBiStreamServer interface {
Send(*Payload) error
Recv() (*Payload, error)
grpc.ServerStream
}
type biRequestStreamRequestBiStreamServer struct {
grpc.ServerStream
}
func (x *biRequestStreamRequestBiStreamServer) Send(m *Payload) error {
return x.ServerStream.SendMsg(m)
}
func (x *biRequestStreamRequestBiStreamServer) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _BiRequestStream_serviceDesc = grpc.ServiceDesc{
ServiceName: "BiRequestStream",
HandlerType: (*BiRequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestBiStream",
Handler: _BiRequestStream_RequestBiStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "nacos_grpc_service.proto",
GoTypes: file_api_proto_nacos_grpc_service_proto_goTypes,
DependencyIndexes: file_api_proto_nacos_grpc_service_proto_depIdxs,
MessageInfos: file_api_proto_nacos_grpc_service_proto_msgTypes,
}.Build()
File_api_proto_nacos_grpc_service_proto = out.File
file_api_proto_nacos_grpc_service_proto_goTypes = nil
file_api_proto_nacos_grpc_service_proto_depIdxs = nil
}

View File

@ -0,0 +1,343 @@
//
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: api/proto/nacos_grpc_service.proto
package auto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
RequestStream_RequestStream_FullMethodName = "/RequestStream/requestStream"
)
// RequestStreamClient is the client API for RequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RequestStreamClient interface {
// build a streamRequest
RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Payload], error)
}
type requestStreamClient struct {
cc grpc.ClientConnInterface
}
func NewRequestStreamClient(cc grpc.ClientConnInterface) RequestStreamClient {
return &requestStreamClient{cc}
}
func (c *requestStreamClient) RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Payload], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &RequestStream_ServiceDesc.Streams[0], RequestStream_RequestStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[Payload, Payload]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type RequestStream_RequestStreamClient = grpc.ServerStreamingClient[Payload]
// RequestStreamServer is the server API for RequestStream service.
// All implementations must embed UnimplementedRequestStreamServer
// for forward compatibility.
type RequestStreamServer interface {
// build a streamRequest
RequestStream(*Payload, grpc.ServerStreamingServer[Payload]) error
mustEmbedUnimplementedRequestStreamServer()
}
// UnimplementedRequestStreamServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedRequestStreamServer struct{}
func (UnimplementedRequestStreamServer) RequestStream(*Payload, grpc.ServerStreamingServer[Payload]) error {
return status.Errorf(codes.Unimplemented, "method RequestStream not implemented")
}
func (UnimplementedRequestStreamServer) mustEmbedUnimplementedRequestStreamServer() {}
func (UnimplementedRequestStreamServer) testEmbeddedByValue() {}
// UnsafeRequestStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RequestStreamServer will
// result in compilation errors.
type UnsafeRequestStreamServer interface {
mustEmbedUnimplementedRequestStreamServer()
}
func RegisterRequestStreamServer(s grpc.ServiceRegistrar, srv RequestStreamServer) {
// If the following call pancis, it indicates UnimplementedRequestStreamServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&RequestStream_ServiceDesc, srv)
}
func _RequestStream_RequestStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Payload)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RequestStreamServer).RequestStream(m, &grpc.GenericServerStream[Payload, Payload]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type RequestStream_RequestStreamServer = grpc.ServerStreamingServer[Payload]
// RequestStream_ServiceDesc is the grpc.ServiceDesc for RequestStream service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var RequestStream_ServiceDesc = grpc.ServiceDesc{
ServiceName: "RequestStream",
HandlerType: (*RequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestStream",
Handler: _RequestStream_RequestStream_Handler,
ServerStreams: true,
},
},
Metadata: "api/proto/nacos_grpc_service.proto",
}
const (
Request_Request_FullMethodName = "/Request/request"
)
// RequestClient is the client API for Request service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RequestClient interface {
// Sends a commonRequest
Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error)
}
type requestClient struct {
cc grpc.ClientConnInterface
}
func NewRequestClient(cc grpc.ClientConnInterface) RequestClient {
return &requestClient{cc}
}
func (c *requestClient) Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Payload)
err := c.cc.Invoke(ctx, Request_Request_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// RequestServer is the server API for Request service.
// All implementations must embed UnimplementedRequestServer
// for forward compatibility.
type RequestServer interface {
// Sends a commonRequest
Request(context.Context, *Payload) (*Payload, error)
mustEmbedUnimplementedRequestServer()
}
// UnimplementedRequestServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedRequestServer struct{}
func (UnimplementedRequestServer) Request(context.Context, *Payload) (*Payload, error) {
return nil, status.Errorf(codes.Unimplemented, "method Request not implemented")
}
func (UnimplementedRequestServer) mustEmbedUnimplementedRequestServer() {}
func (UnimplementedRequestServer) testEmbeddedByValue() {}
// UnsafeRequestServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RequestServer will
// result in compilation errors.
type UnsafeRequestServer interface {
mustEmbedUnimplementedRequestServer()
}
func RegisterRequestServer(s grpc.ServiceRegistrar, srv RequestServer) {
// If the following call pancis, it indicates UnimplementedRequestServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Request_ServiceDesc, srv)
}
func _Request_Request_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Payload)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RequestServer).Request(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Request_Request_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RequestServer).Request(ctx, req.(*Payload))
}
return interceptor(ctx, in, info, handler)
}
// Request_ServiceDesc is the grpc.ServiceDesc for Request service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Request_ServiceDesc = grpc.ServiceDesc{
ServiceName: "Request",
HandlerType: (*RequestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "request",
Handler: _Request_Request_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/proto/nacos_grpc_service.proto",
}
const (
BiRequestStream_RequestBiStream_FullMethodName = "/BiRequestStream/requestBiStream"
)
// BiRequestStreamClient is the client API for BiRequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BiRequestStreamClient interface {
// Sends a commonRequest
RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Payload, Payload], error)
}
type biRequestStreamClient struct {
cc grpc.ClientConnInterface
}
func NewBiRequestStreamClient(cc grpc.ClientConnInterface) BiRequestStreamClient {
return &biRequestStreamClient{cc}
}
func (c *biRequestStreamClient) RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Payload, Payload], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BiRequestStream_ServiceDesc.Streams[0], BiRequestStream_RequestBiStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[Payload, Payload]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BiRequestStream_RequestBiStreamClient = grpc.BidiStreamingClient[Payload, Payload]
// BiRequestStreamServer is the server API for BiRequestStream service.
// All implementations must embed UnimplementedBiRequestStreamServer
// for forward compatibility.
type BiRequestStreamServer interface {
// Sends a commonRequest
RequestBiStream(grpc.BidiStreamingServer[Payload, Payload]) error
mustEmbedUnimplementedBiRequestStreamServer()
}
// UnimplementedBiRequestStreamServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedBiRequestStreamServer struct{}
func (UnimplementedBiRequestStreamServer) RequestBiStream(grpc.BidiStreamingServer[Payload, Payload]) error {
return status.Errorf(codes.Unimplemented, "method RequestBiStream not implemented")
}
func (UnimplementedBiRequestStreamServer) mustEmbedUnimplementedBiRequestStreamServer() {}
func (UnimplementedBiRequestStreamServer) testEmbeddedByValue() {}
// UnsafeBiRequestStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BiRequestStreamServer will
// result in compilation errors.
type UnsafeBiRequestStreamServer interface {
mustEmbedUnimplementedBiRequestStreamServer()
}
func RegisterBiRequestStreamServer(s grpc.ServiceRegistrar, srv BiRequestStreamServer) {
// If the following call pancis, it indicates UnimplementedBiRequestStreamServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&BiRequestStream_ServiceDesc, srv)
}
func _BiRequestStream_RequestBiStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BiRequestStreamServer).RequestBiStream(&grpc.GenericServerStream[Payload, Payload]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BiRequestStream_RequestBiStreamServer = grpc.BidiStreamingServer[Payload, Payload]
// BiRequestStream_ServiceDesc is the grpc.ServiceDesc for BiRequestStream service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BiRequestStream_ServiceDesc = grpc.ServiceDesc{
ServiceName: "BiRequestStream",
HandlerType: (*BiRequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestBiStream",
Handler: _BiRequestStream_RequestBiStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "api/proto/nacos_grpc_service.proto",
}

View File

@ -21,7 +21,8 @@ import "google/protobuf/any.proto";
option java_multiple_files = true;
option java_package = "com.alibaba.nacos.api.grpc.auto";
option go_package = "nacos/api/grpc/auto";
option go_package = "github.com/nacos-group/nacos-sdk-go/v2/api/grpc/auto;auto";
message Metadata {
string type = 3;

View File

@ -45,7 +45,7 @@ func NewConfigClient(param vo.NacosClientParam) (iClient config_client.IConfigCl
if err != nil {
return
}
config, err := config_client.NewConfigClient(nacosClient)
config, err := config_client.NewConfigClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
@ -58,7 +58,7 @@ func NewNamingClient(param vo.NacosClientParam) (iClient naming_client.INamingCl
if err != nil {
return
}
naming, err := naming_client.NewNamingClient(nacosClient)
naming, err := naming_client.NewNamingClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}

View File

@ -19,6 +19,7 @@ package config_client
import (
"context"
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"os"
"strings"
"sync"
@ -59,6 +60,7 @@ type ConfigClient struct {
cacheMap cache.ConcurrentMap
uid string
listenExecute chan struct{}
isClosed bool
}
type cacheData struct {
@ -101,7 +103,7 @@ func (cacheData *cacheData) executeListener() {
go cacheData.cacheDataListener.listener(cacheData.tenant, cacheData.group, cacheData.dataId, decryptedContent)
}
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
func NewConfigClientWithRamCredentialProvider(nc nacos_client.INacosClient, provider security.RamCredentialProvider) (*ConfigClient, error) {
config := &ConfigClient{}
config.ctx, config.cancel = context.WithCancel(context.Background())
config.INacosClient = nc
@ -124,7 +126,7 @@ func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
clientConfig.CacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configCacheDir = clientConfig.CacheDir
if config.configProxy, err = NewConfigProxy(config.ctx, serverConfig, clientConfig, httpAgent); err != nil {
if config.configProxy, err = NewConfigProxyWithRamCredentialProvider(config.ctx, serverConfig, clientConfig, httpAgent, provider); err != nil {
return nil, err
}
@ -152,6 +154,10 @@ func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
return config, err
}
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
return NewConfigClientWithRamCredentialProvider(nc, nil)
}
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
@ -247,6 +253,7 @@ func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool,
clientConfig, _ := client.GetClientConfig()
request := rpc_request.NewConfigPublishRequest(param.Group, param.DataId, clientConfig.NamespaceId, param.Content, param.CasMd5)
request.AdditionMap["tag"] = param.Tag
request.AdditionMap["config_tags"] = param.ConfigTags
request.AdditionMap["appName"] = param.AppName
request.AdditionMap["betaIps"] = param.BetaIps
request.AdditionMap["type"] = param.Type
@ -358,8 +365,15 @@ func (client *ConfigClient) SearchConfig(param vo.SearchConfigParam) (*model.Con
}
func (client *ConfigClient) CloseClient() {
client.mutex.Lock()
defer client.mutex.Unlock()
if client.isClosed {
return
}
client.configProxy.getRpcClient(client).Shutdown()
client.cancel()
client.isClosed = true
}
func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) {

View File

@ -19,6 +19,7 @@ package config_client
import (
"context"
"errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
@ -356,3 +357,55 @@ func TestCancelListenConfig(t *testing.T) {
assert.Nil(t, err)
})
}
type MockAccessKeyCredentialProvider struct {
accessKey string
secretKey string
signatureRegionId string
}
func (provider *MockAccessKeyCredentialProvider) MatchProvider() bool {
return true
}
func (provider *MockAccessKeyCredentialProvider) Init() error {
return nil
}
func (provider *MockAccessKeyCredentialProvider) GetCredentialsForNacosClient() security.RamContext {
ramContext := security.RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SignatureRegionId: "",
}
return ramContext
}
func Test_ConfigClientWithProvider(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
clientConfigWithOptions.AccessKey = ""
clientConfigWithOptions.SecretKey = ""
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
provider := &MockAccessKeyCredentialProvider{
accessKey: "LTAxxx",
secretKey: "EdPxxx",
}
client, _ := NewConfigClientWithRamCredentialProvider(&nc, provider)
client.configProxy = &MockConfigProxy{}
success, err := client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
assert.Nil(t, err)
assert.Equal(t, "hello world", content)
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* 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.
*/
package config_client
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
type ConfigConnectionEventListener struct {
client *ConfigClient
taskId string
}
func NewConfigConnectionEventListener(client *ConfigClient, taskId string) *ConfigConnectionEventListener {
return &ConfigConnectionEventListener{
client: client,
taskId: taskId,
}
}
func (c *ConfigConnectionEventListener) OnConnected() {
logger.Info("[ConfigConnectionEventListener] connect to config server for taskId: " + c.taskId)
if c.client != nil {
c.client.asyncNotifyListenConfig()
}
}
func (c *ConfigConnectionEventListener) OnDisConnect() {
logger.Info("[ConfigConnectionEventListener] disconnect from config server for taskId: " + c.taskId)
if c.client != nil {
taskIdInt, err := strconv.Atoi(c.taskId)
if err != nil {
logger.Errorf("[ConfigConnectionEventListener] parse taskId error: %v", err)
return
}
items := c.client.cacheMap.Items()
for key, v := range items {
if data, ok := v.(cacheData); ok {
if data.taskId == taskIdInt {
data.isSyncWithServer = false
c.client.cacheMap.Set(key, data)
}
}
}
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* 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.
*/
package config_client
import (
"context"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestNewConfigConnectionEventListener(t *testing.T) {
client := &ConfigClient{}
taskId := "123"
listener := NewConfigConnectionEventListener(client, taskId)
assert.Equal(t, client, listener.client)
assert.Equal(t, taskId, listener.taskId)
}
func TestOnDisConnectWithMock(t *testing.T) {
client := &ConfigClient{
cacheMap: cache.NewConcurrentMap(),
}
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data2 := cacheData{
dataId: "dataId2",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data3 := cacheData{
dataId: "dataId3",
group: "group2",
tenant: "",
taskId: 2,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
key2 := util.GetConfigCacheKey(data2.dataId, data2.group, data2.tenant)
key3 := util.GetConfigCacheKey(data3.dataId, data3.group, data3.tenant)
client.cacheMap.Set(key1, data1)
client.cacheMap.Set(key2, data2)
client.cacheMap.Set(key3, data3)
listener := NewConfigConnectionEventListener(client, "1")
listener.OnDisConnect()
item1, _ := client.cacheMap.Get(key1)
item2, _ := client.cacheMap.Get(key2)
item3, _ := client.cacheMap.Get(key3)
updatedData1 := item1.(cacheData)
updatedData2 := item2.(cacheData)
updatedData3 := item3.(cacheData)
assert.False(t, updatedData1.isSyncWithServer, "dataId1 should be marked as not sync")
assert.False(t, updatedData2.isSyncWithServer, "dataId2 should be marked as not sync")
assert.True(t, updatedData3.isSyncWithServer, "dataId3 should be marked as sync")
}
func TestOnConnectedWithMock(t *testing.T) {
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
listenExecute: listenChan,
}
listener := NewConfigConnectionEventListener(client, "1")
listener.OnConnected()
time.Sleep(100 * time.Millisecond)
select {
case <-listenChan:
assert.True(t, true, "asyncNotifyListenConfig should be called")
default:
t.Fatalf("asyncNotifyListenConfig should be called but not")
}
}
type MockRpcClientForListener struct {
requestCalled rpc_request.IRequest
}
func (m *MockRpcClientForListener) Request(request rpc_request.IRequest) (rpc_response.IResponse, error) {
m.requestCalled = request
return &rpc_response.ConfigChangeBatchListenResponse{
Response: &rpc_response.Response{
ResultCode: 200,
},
ChangedConfigs: []model.ConfigContext{},
}, nil
}
func TestReconnectionFlow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mockRpc := &MockRpcClientForListener{}
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
ctx: ctx,
configProxy: &MockConfigProxy{},
cacheMap: cache.NewConcurrentMap(),
listenExecute: listenChan,
}
done := make(chan bool)
go func() {
for {
select {
case <-listenChan:
mockRpc.Request(&rpc_request.ConfigBatchListenRequest{})
done <- true
case <-ctx.Done():
return
}
}
}()
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
client.cacheMap.Set(key1, data1)
listener := NewConfigConnectionEventListener(client, "1")
initialData, _ := client.cacheMap.Get(key1)
assert.True(t, initialData.(cacheData).isSyncWithServer, "initial data should be sync with server")
listener.OnDisConnect()
afterDisconnectData, _ := client.cacheMap.Get(key1)
assert.False(t, afterDisconnectData.(cacheData).isSyncWithServer, "disconnect should set isSyncWithServer to false")
listener.OnConnected()
select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatalf("wait for done timeout")
}
assert.NotNil(t, mockRpc.requestCalled, "should call request")
_, ok := mockRpc.requestCalled.(*rpc_request.ConfigBatchListenRequest)
assert.True(t, ok, "should be a ConfigBatchListenRequest")
}

View File

@ -23,22 +23,20 @@ import (
"strconv"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
)
type ConfigProxy struct {
@ -47,20 +45,21 @@ type ConfigProxy struct {
}
func NewConfigProxy(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (IConfigProxy, error) {
return NewConfigProxyWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, nil)
}
func NewConfigProxyWithRamCredentialProvider(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent, provider security.RamCredentialProvider) (IConfigProxy, error) {
proxy := ConfigProxy{}
var err error
proxy.nacosServer, err = nacos_server.NewNacosServer(ctx, serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint, nil)
proxy.nacosServer, err = nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint, nil, provider)
proxy.clientConfig = clientConfig
return &proxy, err
}
func (cp *ConfigProxy) requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error) {
start := time.Now()
cp.nacosServer.InjectSecurityInfo(request.GetHeaders())
cp.nacosServer.InjectSecurityInfo(request.GetHeaders(), security.BuildConfigResourceByRequest(request))
cp.injectCommHeader(request.GetHeaders())
cp.nacosServer.InjectSkAk(request.GetHeaders(), cp.clientConfig)
signHeaders := nacos_server.GetSignHeadersFromRequest(request.(rpc_request.IConfigRequest), cp.clientConfig.SecretKey)
request.PutAllHeaders(signHeaders)
response, err := rpcClient.Request(request, int64(timeoutMills))
monitor.GetConfigRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err
@ -87,14 +86,27 @@ func (cp *ConfigProxy) searchConfigProxy(param vo.SearchConfigParam, tenant, acc
params["dataId"] = ""
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
var version = "v2"
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
if len(tenant) > 0 {
params["namespaceId"] = params["tenant"]
}
params["groupName"] = params["group"]
result, err = cp.nacosServer.ReqConfigApi("/v3/admin/cs/config/list", params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return nil, err
}
version = "v3"
}
var configPage model.ConfigPage
if version == "v2" {
err = json.Unmarshal([]byte(result), &configPage)
} else {
var configPageResult model.ConfigPageResult
err = json.Unmarshal([]byte(result), &configPageResult)
configPage = configPageResult.Data
}
if err != nil {
return nil, err
}
@ -133,6 +145,7 @@ func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64,
if response.GetErrorCode() == 300 {
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, "")
cache.WriteEncryptedDataKeyToFile(cacheKey, cp.clientConfig.CacheDir, "")
response.SetSuccess(true)
return response, nil
}
@ -173,6 +186,10 @@ func (cp *ConfigProxy) createRpcClient(ctx context.Context, taskId string, clien
// TODO fix the group/dataId empty problem
return rpc_request.NewConfigChangeNotifyRequest("", "", "")
}, &ConfigChangeNotifyRequestHandler{client: client})
configListener := NewConfigConnectionEventListener(client, taskId)
rpcClient.RegisterConnectionListener(configListener)
rpcClient.Tenant = cp.clientConfig.NamespaceId
rpcClient.Start()
}

View File

@ -115,12 +115,12 @@ func (s *ServiceInfoHolder) GetServiceInfo(serviceName, groupName, clusters stri
return model.Service{}, ok
}
func (s *ServiceInfoHolder) RegisterCallback(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
s.subCallback.AddCallbackFunc(serviceName, clusters, callbackFunc)
func (s *ServiceInfoHolder) RegisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.AddCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) DeregisterCallback(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
s.subCallback.RemoveCallbackFunc(serviceName, clusters, callbackFunc)
func (s *ServiceInfoHolder) DeregisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.RemoveCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) StopUpdateIfContain(serviceName, clusters string) {

View File

@ -36,31 +36,34 @@ func NewSubscribeCallback() *SubscribeCallback {
func (ed *SubscribeCallback) IsSubscribed(serviceName, clusters string) bool {
key := util.GetServiceCacheKey(serviceName, clusters)
_, ok := ed.callbackFuncMap.Get(key)
return ok
funcs, ok := ed.callbackFuncMap.Get(key)
if ok {
return len(funcs.([]*SubscribeCallbackFuncWrapper)) > 0
}
return false
}
func (ed *SubscribeCallback) AddCallbackFunc(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
func (ed *SubscribeCallback) AddCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
key := util.GetServiceCacheKey(serviceName, clusters)
ed.mux.Lock()
defer ed.mux.Unlock()
var funcSlice []*func(services []model.Instance, err error)
var funcSlice []*SubscribeCallbackFuncWrapper
old, ok := ed.callbackFuncMap.Get(key)
if ok {
funcSlice = append(funcSlice, old.([]*func(services []model.Instance, err error))...)
funcSlice = append(funcSlice, old.([]*SubscribeCallbackFuncWrapper)...)
}
funcSlice = append(funcSlice, callbackFunc)
funcSlice = append(funcSlice, callbackWrapper)
ed.callbackFuncMap.Set(key, funcSlice)
}
func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
logger.Info("removing " + serviceName + " with " + clusters + " to listener map")
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncMap.Get(key)
if ok && funcs != nil {
var newFuncs []*func(services []model.Instance, err error)
for _, funcItem := range funcs.([]*func(services []model.Instance, err error)) {
if funcItem != callbackFunc {
var newFuncs []*SubscribeCallbackFuncWrapper
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
if funcItem.CallbackFunc != callbackWrapper.CallbackFunc || !funcItem.Selector.Equals(callbackWrapper.Selector) {
newFuncs = append(newFuncs, funcItem)
}
}
@ -72,8 +75,8 @@ func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters str
func (ed *SubscribeCallback) ServiceChanged(cacheKey string, service *model.Service) {
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if ok {
for _, funcItem := range funcs.([]*func(services []model.Instance, err error)) {
(*funcItem)(service.Hosts, nil)
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
funcItem.notifyListener(service)
}
}
}

View File

@ -58,13 +58,15 @@ func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
fmt.Println(util.ToJsonString(ed.callbackFuncMap))
},
}
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.Instance, err error))
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
@ -98,7 +100,9 @@ func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
fmt.Printf("func1:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 1")
param2 := vo.SubscribeParam{
@ -109,21 +113,23 @@ func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
fmt.Printf("func2:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 2")
for k, v := range ed.callbackFuncMap.Items() {
log.Printf("key:%s,%d", k, len(v.([]*func(services []model.Instance, err error))))
log.Printf("key:%s,%d", k, len(v.([]*SubscribeCallbackFuncWrapper)))
}
ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.Instance, err error))
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
@ -158,7 +164,9 @@ func TestSubscribeCallback_ServiceChanged(t *testing.T) {
log.Printf("func1:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
param2 := vo.SubscribeParam{
ServiceName: "Test",
@ -169,7 +177,54 @@ func TestSubscribeCallback_ServiceChanged(t *testing.T) {
},
}
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
ed.ServiceChanged(cacheKey, &service)
}
func TestSubscribeCallback_RemoveCallbackFunc(t *testing.T) {
ed := NewSubscribeCallback()
serviceName := "Test"
clusters := "default"
groupName := "public"
callback1 := func(services []model.Instance, err error) {
log.Printf("callback1:%s \n", util.ToJsonString(services))
}
clusterSelector1 := NewClusterSelector([]string{clusters})
callbackWrapper1 := NewSubscribeCallbackFuncWrapper(clusterSelector1, &callback1)
callback2 := func(services []model.Instance, err error) {
log.Printf("callback2:%s \n", util.ToJsonString(services))
}
clusterSelector2 := NewClusterSelector([]string{clusters})
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &callback2)
// Add both callbacks
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the first callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
// Check if only the second callback remains
cacheKey := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if !ok || len(funcs.([]*SubscribeCallbackFuncWrapper)) != 1 {
t.Errorf("Expected 1 callback function, got %d", len(funcs.([]*SubscribeCallbackFuncWrapper)))
}
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the second callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
// Check if no callbacks remain
funcs, ok = ed.callbackFuncMap.Get(cacheKey)
if ok && len(funcs.([]*SubscribeCallbackFuncWrapper)) != 0 {
t.Errorf("Expected 0 callback functions, got %d", len(funcs.([]*func(services []model.Instance, err error))))
}
assert.False(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
}

View File

@ -0,0 +1,106 @@
package naming_cache
import (
"sort"
"strings"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type Selector interface {
SelectInstance(service *model.Service) []model.Instance
Equals(o Selector) bool
}
type ClusterSelector struct {
ClusterNames string
Clusters []string
}
func NewClusterSelector(clusters []string) *ClusterSelector {
if len(clusters) == 0 {
return &ClusterSelector{
ClusterNames: "",
Clusters: []string{},
}
}
// 创建副本避免外部修改
clustersCopy := make([]string, len(clusters))
copy(clustersCopy, clusters)
return &ClusterSelector{
ClusterNames: joinCluster(clusters),
Clusters: clustersCopy,
}
}
func NewSubscribeCallbackFuncWrapper(selector Selector, callback *func(services []model.Instance, err error)) *SubscribeCallbackFuncWrapper {
if selector == nil {
panic("selector cannot be nil")
}
if callback == nil {
panic("callback cannot be nil")
}
return &SubscribeCallbackFuncWrapper{
Selector: selector,
CallbackFunc: callback,
}
}
type SubscribeCallbackFuncWrapper struct {
Selector Selector
CallbackFunc *func(services []model.Instance, err error)
}
func (ed *SubscribeCallbackFuncWrapper) notifyListener(service *model.Service) {
instances := ed.Selector.SelectInstance(service)
if ed.CallbackFunc != nil {
(*ed.CallbackFunc)(instances, nil)
}
}
func (cs *ClusterSelector) SelectInstance(service *model.Service) []model.Instance {
var instances []model.Instance
if cs.ClusterNames == "" {
return service.Hosts
}
for _, instance := range service.Hosts {
if util.Contains(cs.Clusters, instance.ClusterName) {
instances = append(instances, instance)
}
}
return instances
}
func (cs *ClusterSelector) Equals(o Selector) bool {
if o == nil {
return false
}
if o, ok := o.(*ClusterSelector); ok {
return cs.ClusterNames == o.ClusterNames
}
return false
}
func joinCluster(cluster []string) string {
// 使用map实现去重
uniqueSet := make(map[string]struct{})
for _, item := range cluster {
if item != "" { // 过滤空字符串类似Java中的isNotEmpty
uniqueSet[item] = struct{}{}
}
}
uniqueSlice := make([]string, 0, len(uniqueSet))
for item := range uniqueSet {
uniqueSlice = append(uniqueSlice, item)
}
sort.Strings(uniqueSlice)
// 使用逗号连接
return strings.Join(uniqueSlice, ",")
}

View File

@ -21,8 +21,11 @@ import (
"math"
"math/rand"
"strings"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
@ -42,10 +45,17 @@ type NamingClient struct {
cancel context.CancelFunc
serviceProxy naming_proxy.INamingProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
isClosed bool
mutex sync.Mutex
}
// NewNamingClient ...
func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
return NewNamingClientWithRamCredentialProvider(nc, nil)
}
// NewNamingClientWithRamCredentialProvider ...
func NewNamingClientWithRamCredentialProvider(nc nacos_client.INacosClient, provider security.RamCredentialProvider) (*NamingClient, error) {
ctx, cancel := context.WithCancel(context.Background())
rand.Seed(time.Now().UnixNano())
naming := &NamingClient{INacosClient: nc, ctx: ctx, cancel: cancel}
@ -75,7 +85,7 @@ func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
naming.serviceInfoHolder = naming_cache.NewServiceInfoHolder(clientConfig.NamespaceId, clientConfig.CacheDir,
clientConfig.UpdateCacheWhenEmpty, clientConfig.NotLoadCacheAtStart)
naming.serviceProxy, err = NewNamingProxyDelegate(ctx, clientConfig, serverConfig, httpAgent, naming.serviceInfoHolder)
naming.serviceProxy, err = NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientConfig, serverConfig, httpAgent, naming.serviceInfoHolder, provider)
if clientConfig.AsyncUpdateService {
go NewServiceInfoUpdater(ctx, naming.serviceInfoHolder, clientConfig.UpdateThreadNum, naming.serviceProxy).asyncUpdateService()
@ -191,11 +201,14 @@ func (sc *NamingClient) GetService(param vo.GetServiceParam) (service model.Serv
param.GroupName = constant.DEFAULT_GROUP
}
var ok bool
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
service.Clusters = clusters
service.Hosts = clusterSelector.SelectInstance(&service)
return service, err
}
@ -221,21 +234,24 @@ func (sc *NamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
clusters := strings.Join(param.Clusters, ",")
var (
service model.Service
ok bool
err error
)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
if err != nil || service.Hosts == nil || len(service.Hosts) == 0 {
if err != nil {
return []model.Instance{}, err
}
return service.Hosts, err
instances := clusterSelector.SelectInstance(&service)
if instances == nil || len(instances) == 0 {
return []model.Instance{}, err
}
return instances, err
}
// SelectInstances Get all instance by DataId, Group and Health
@ -248,14 +264,15 @@ func (sc *NamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.
ok bool
err error
)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectInstances(service, param.HealthyOnly)
}
@ -284,15 +301,15 @@ func (sc *NamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanc
ok bool
err error
)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectOneHealthyInstances(service)
}
@ -325,19 +342,21 @@ func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
clusters := strings.Join(param.Clusters, ",")
sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), clusters, &param.SubscribeCallback)
_, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), "", callbackWrapper)
_, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
return err
}
// Unsubscribe ...
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) (err error) {
clusters := strings.Join(param.Clusters, ",")
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
serviceFullName := util.GetGroupName(param.ServiceName, param.GroupName)
sc.serviceInfoHolder.DeregisterCallback(serviceFullName, clusters, &param.SubscribeCallback)
if sc.serviceInfoHolder.IsSubscribed(serviceFullName, clusters) {
err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, clusters)
sc.serviceInfoHolder.DeregisterCallback(serviceFullName, "", callbackWrapper)
if !sc.serviceInfoHolder.IsSubscribed(serviceFullName, "") {
err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, "")
}
return err
@ -350,6 +369,13 @@ func (sc *NamingClient) ServerHealthy() bool {
// CloseClient ...
func (sc *NamingClient) CloseClient() {
sc.mutex.Lock()
defer sc.mutex.Unlock()
if sc.isClosed {
return
}
sc.serviceProxy.CloseClient()
sc.cancel()
sc.isClosed = true
}

View File

@ -37,6 +37,8 @@ var clientConfigTest = *constant.NewClientConfig(
var serverConfigTest = *constant.NewServerConfig("127.0.0.1", 80, constant.WithContextPath("/nacos"))
type MockNamingProxy struct {
unsubscribeCalled bool
unsubscribeParams []string // 记录调用参数
}
func (m *MockNamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
@ -68,6 +70,8 @@ func (m *MockNamingProxy) Subscribe(serviceName, groupName, clusters string) (mo
}
func (m *MockNamingProxy) Unsubscribe(serviceName, groupName, clusters string) error {
m.unsubscribeCalled = true
m.unsubscribeParams = []string{serviceName, groupName, clusters}
return nil
}
@ -452,3 +456,108 @@ func BenchmarkNamingClient_SelectOneHealthyInstances(b *testing.B) {
}
}
func TestNamingClient_Unsubscribe_WithCallback_ShouldNotCallServiceProxyUnsubscribe(t *testing.T) {
// 创建一个带有回调函数的订阅参数
callback := func(services []model.Instance, err error) {
// 空回调函数
}
param := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback,
}
// 创建测试客户端
client := NewTestNamingClient()
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 执行 Unsubscribe
err := client.Unsubscribe(param)
// 验证没有错误
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}
func TestNamingClient_Unsubscribe_WithoutCallback_ShouldCallServiceProxyUnsubscribe(t *testing.T) {
// 创建一个没有回调函数的订阅参数
param := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
// SubscribeCallback 为 nil
}
// 创建测试客户端
client := NewTestNamingClient()
// 获取原始的 MockNamingProxy 来检查调用状态
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 执行 Unsubscribe
err := client.Unsubscribe(param)
// 验证没有错误
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}
// TestNamingClient_Unsubscribe_Integration_Test 集成测试,使用真实的 ServiceInfoHolder 来测试修复后的逻辑
func TestNamingClient_Unsubscribe_Integration_Test(t *testing.T) {
// 创建测试客户端
client := NewTestNamingClient()
// 获取原始的 MockNamingProxy 来检查调用状态
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 创建回调函数
callback1 := func(services []model.Instance, err error) {
// 回调函数1
}
callback2 := func(services []model.Instance, err error) {
// 回调函数2
}
// 测试场景1先注册两个回调函数然后取消订阅第一个
// 这种情况下,取消订阅第一个回调函数后,还有其他回调函数,所以不应该调用 serviceProxy.Unsubscribe
// 注册第一个回调函数
param1 := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback1,
}
// 注册第二个回调函数
param2 := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback2,
}
// 先注册两个回调函数
err := client.Subscribe(param1)
assert.Nil(t, err)
err = client.Subscribe(param2)
assert.Nil(t, err)
// 重置 MockNamingProxy 的调用状态
mockProxy.unsubscribeCalled = false
mockProxy.unsubscribeParams = nil
// 取消订阅第一个回调函数
err = client.Unsubscribe(param1)
assert.Nil(t, err)
assert.False(t, mockProxy.unsubscribeCalled)
// 取消订阅第二个回调函数
err = client.Unsubscribe(param2)
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}

View File

@ -1,14 +1,14 @@
package naming_grpc
import (
"testing"
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
)
func TestRedoSubscribe(t *testing.T) {
t.Skip("Skipping test,It failed due to a previous commit and is difficult to modify because of the use of struct type assertions in the code.")
ctrl := gomock.NewController(t)
defer ctrl.Finish()

View File

@ -28,6 +28,7 @@ import (
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
@ -83,8 +84,7 @@ func NewNamingGrpcProxy(ctx context.Context, clientCfg constant.ClientConfig, na
func (proxy *NamingGrpcProxy) requestToServer(request rpc_request.IRequest) (rpc_response.IResponse, error) {
start := time.Now()
proxy.nacosServer.InjectSign(request, request.GetHeaders(), proxy.clientConfig)
proxy.nacosServer.InjectSecurityInfo(request.GetHeaders())
proxy.nacosServer.InjectSecurityInfo(request.GetHeaders(), security.BuildNamingResourceByRequest(request))
response, err := proxy.rpcClient.GetRpcClient().Request(request, int64(proxy.clientConfig.TimeoutMs))
monitor.GetNamingRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err

View File

@ -18,6 +18,7 @@ package naming_client
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
@ -40,6 +41,11 @@ type NamingProxyDelegate struct {
func NewNamingProxyDelegate(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder) (naming_proxy.INamingProxy, error) {
return NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientCfg, serverCfgs, httpAgent, serviceInfoHolder, nil)
}
func NewNamingProxyDelegateWithRamCredentialProvider(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder, provider security.RamCredentialProvider) (naming_proxy.INamingProxy, error) {
uid, err := uuid.NewV4()
if err != nil {
@ -51,7 +57,7 @@ func NewNamingProxyDelegate(ctx context.Context, clientCfg constant.ClientConfig
"RequestId": {uid.String()},
"Request-Module": {"Naming"},
}
nacosServer, err := nacos_server.NewNacosServer(ctx, serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint, namingHeader)
nacosServer, err := nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint, namingHeader, provider)
if err != nil {
return nil, err
}

View File

@ -123,6 +123,12 @@ func WithSecretKey(secretKey string) ClientOption {
}
}
func WithRamConfig(ramConfig *RamConfig) ClientOption {
return func(config *ClientConfig) {
config.RamConfig = ramConfig
}
}
// WithOpenKMS ...
func WithOpenKMS(openKMS bool) ClientOption {
return func(config *ClientConfig) {

View File

@ -37,6 +37,7 @@ type ClientConfig struct {
RegionId string // the regionId for kms
AccessKey string // the AccessKey for kms
SecretKey string // the SecretKey for kms
RamConfig *RamConfig
OpenKMS bool // it's to open kms, default is false. https://help.aliyun.com/product/28933.html
KMSVersion KMSVersion // kms client version. https://help.aliyun.com/document_detail/380927.html
KMSv3Config *KMSv3Config //KMSv3 configuration. https://help.aliyun.com/document_detail/601596.html
@ -117,3 +118,17 @@ type KMSConfig struct {
OpenSSL string
CaContent string
}
type RamConfig struct {
SecurityToken string
SignatureRegionId string
RamRoleName string
RoleArn string
Policy string
RoleSessionName string
RoleSessionExpiration int
OIDCProviderArn string
OIDCTokenFilePath string
CredentialsURI string
SecretName string
}

View File

@ -76,7 +76,7 @@ const (
KEY_BEAT = "beat"
KEY_DOM = "dom"
DEFAULT_CONTEXT_PATH = "/nacos"
CLIENT_VERSION = "Nacos-Go-Client:v2.2.7"
CLIENT_VERSION = "Nacos-Go-Client:v2.3.3"
REQUEST_DOMAIN_RETRY_TIME = 3
SERVICE_INFO_SPLITER = "@@"
CONFIG_INFO_SPLITER = "@@"
@ -107,4 +107,15 @@ const (
GRPC = "grpc"
RpcPortOffset = 1000
MSE_KMSv1_DEFAULT_KEY_ID = "alias/acs/mse"
CONFIG_PUBLISH_REQUEST_NAME = "ConfigPublishRequest"
CONFIG_QUERY_REQUEST_NAME = "ConfigQueryRequest"
CONFIG_REMOVE_REQUEST_NAME = "ConfigRemoveRequest"
INSTANCE_REQUEST_NAME = "InstanceRequest"
BATCH_INSTANCE_REQUEST_NAME = "BatchInstanceRequest"
SERVICE_LIST_REQUEST_NAME = "ServiceListRequest"
SERVICE_QUERY_REQUEST_NAME = "ServiceQueryRequest"
SUBSCRIBE_SERVICE_REQUEST_NAME = "SubscribeServiceRequest"
NOTIFY_SUBSCRIBE_REQUEST_NAME = "NotifySubscriberRequest"
CONFIG_BATCH_LISTEN_REQUEST_NAME = "ConfigBatchListenRequest"
CONFIG_CHANGE_NOTIFY_REQUEST_NAME = "ConfigChangeNotifyRequest"
)

View File

@ -110,6 +110,12 @@ func BuildLoggerConfig(clientConfig constant.ClientConfig) Config {
loggerConfig.LogRollingConfig.MaxBackups = logRollingConfig.MaxBackups
loggerConfig.LogRollingConfig.LocalTime = logRollingConfig.LocalTime
loggerConfig.LogRollingConfig.Compress = logRollingConfig.Compress
} else {
loggerConfig.LogRollingConfig.MaxSize = 100
loggerConfig.LogRollingConfig.MaxAge = 30
loggerConfig.LogRollingConfig.MaxBackups = 5
loggerConfig.LogRollingConfig.LocalTime = true
loggerConfig.LogRollingConfig.Compress = false
}
return loggerConfig
}

View File

@ -18,9 +18,6 @@ package nacos_server
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"io"
"math/rand"
"net/http"
@ -33,8 +30,6 @@ import (
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
@ -48,7 +43,7 @@ import (
type NacosServer struct {
sync.RWMutex
securityLogin security.AuthClient
securityLogin security.SecurityProxy
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
@ -65,12 +60,16 @@ type NacosServer struct {
}
func NewNacosServer(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string) (*NacosServer, error) {
return NewNacosServerWithRamCredentialProvider(ctx, serverList, clientCfg, httpAgent, timeoutMs, endpoint, endpointQueryHeader, nil)
}
func NewNacosServerWithRamCredentialProvider(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string, provider security.RamCredentialProvider) (*NacosServer, error) {
severLen := len(serverList)
if severLen == 0 && endpoint == "" {
return &NacosServer{}, errors.New("both serverlist and endpoint are empty")
}
securityLogin := security.NewAuthClient(clientCfg, serverList, httpAgent)
securityLogin := security.NewSecurityProxyWithRamCredentialProvider(clientCfg, serverList, httpAgent, provider)
ns := NacosServer{
serverList: serverList,
@ -92,12 +91,7 @@ func NewNacosServer(ctx context.Context, serverList []constant.ServerConfig, cli
ns.initRefreshSrvIfNeed(ctx)
}
_, err := ns.securityLogin.Login()
if err != nil {
logger.Errorf("login in err:%v", err)
}
ns.securityLogin.Login()
ns.securityLogin.AutoRefresh(ctx)
return &ns, nil
}
@ -109,8 +103,6 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
contextPath = constant.WEB_CONTEXT
}
signHeaders := GetSignHeaders(params, newHeaders["secretKey"])
url := curServer + contextPath + api
headers := map[string][]string{}
@ -130,10 +122,6 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
}
headers["RequestId"] = []string{uid.String()}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
headers["Spas-AccessKey"] = []string{newHeaders["accessKey"]}
headers["Timestamp"] = []string{signHeaders["Timestamp"]}
headers["Spas-Signature"] = []string{signHeaders["Spas-Signature"]}
server.InjectSecurityInfo(params)
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, timeoutMS, params)
@ -177,8 +165,6 @@ func (server *NacosServer) callServer(api string, params map[string]string, meth
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
server.InjectSecurityInfo(params)
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
if err != nil {
@ -206,7 +192,7 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params)
server.InjectSecurityInfo(params, security.BuildConfigResource(params["tenant"], params["group"], params["dataId"]))
//only one server,retry request when error
var err error
@ -240,8 +226,7 @@ func (server *NacosServer) ReqApi(api string, params map[string]string, method s
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params)
server.InjectSignForNamingHttp(params, config)
server.InjectSecurityInfo(params, security.BuildNamingResource(params["namespaceId"], params["serviceName"], params["groupName"]))
//only one server,retry request when error
var err error
@ -356,47 +341,13 @@ func (server *NacosServer) GetServerList() []constant.ServerConfig {
return server.serverList
}
func (server *NacosServer) InjectSecurityInfo(param map[string]string) {
accessToken := server.securityLogin.GetAccessToken()
if accessToken != "" {
param[constant.KEY_ACCESS_TOKEN] = accessToken
func (server *NacosServer) InjectSecurityInfo(param map[string]string, resource security.RequestResource) {
securityInfo := server.securityLogin.GetSecurityInfo(resource)
for k, v := range securityInfo {
param[k] = v
}
}
func (server *NacosServer) InjectSignForNamingHttp(param map[string]string, clientConfig constant.ClientConfig) {
if clientConfig.AccessKey == "" || clientConfig.SecretKey == "" {
return
}
var signData string
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
if serviceName, hasServiceName := param["serviceName"]; hasServiceName {
if groupName, hasGroup := param["groupName"]; strings.Contains(serviceName, constant.SERVICE_INFO_SPLITER) || !hasGroup || groupName == "" {
signData = timeStamp + constant.SERVICE_INFO_SPLITER + serviceName
} else {
signData = timeStamp + constant.SERVICE_INFO_SPLITER + util.GetGroupName(serviceName, groupName)
}
} else {
signData = timeStamp
}
param["signature"] = signWithhmacSHA1Encrypt(signData, clientConfig.SecretKey)
param["ak"] = clientConfig.AccessKey
param["data"] = signData
}
func (server *NacosServer) InjectSign(request rpc_request.IRequest, param map[string]string, clientConfig constant.ClientConfig) {
if clientConfig.AccessKey == "" || clientConfig.SecretKey == "" {
return
}
sts := request.GetStringToSign()
if sts == "" {
return
}
signature := signWithhmacSHA1Encrypt(sts, clientConfig.SecretKey)
param["data"] = sts
param["signature"] = signature
param["ak"] = clientConfig.AccessKey
}
func getAddress(cfg constant.ServerConfig) string {
if strings.Index(cfg.IpAddr, "http://") >= 0 || strings.Index(cfg.IpAddr, "https://") >= 0 {
return cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
@ -404,69 +355,6 @@ func getAddress(cfg constant.ServerConfig) string {
return cfg.Scheme + "://" + cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
func GetSignHeadersFromRequest(cr rpc_request.IConfigRequest, secretKey string) map[string]string {
resource := ""
if len(cr.GetTenant()) != 0 {
resource = cr.GetTenant() + "+" + cr.GetGroup()
} else {
resource = cr.GetGroup()
}
headers := map[string]string{}
timeStamp := strconv.FormatInt(util.CurrentMillis(), 10)
headers["Timestamp"] = timeStamp
signature := ""
if resource == "" {
signature = signWithhmacSHA1Encrypt(timeStamp, secretKey)
} else {
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, secretKey)
}
headers["Spas-Signature"] = signature
return headers
}
func GetSignHeaders(params map[string]string, secretKey string) map[string]string {
resource := ""
if len(params["tenant"]) != 0 {
resource = params["tenant"] + "+" + params["group"]
} else {
resource = params["group"]
}
headers := map[string]string{}
timeStamp := strconv.FormatInt(util.CurrentMillis(), 10)
headers["Timestamp"] = timeStamp
signature := ""
if resource == "" {
signature = signWithhmacSHA1Encrypt(timeStamp, secretKey)
} else {
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, secretKey)
}
headers["Spas-Signature"] = signature
return headers
}
func signWithhmacSHA1Encrypt(encryptText, encryptKey string) string {
//hmac ,use sha1
key := []byte(encryptKey)
mac := hmac.New(sha1.New, key)
mac.Write([]byte(encryptText))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func (server *NacosServer) GetNextServer() (constant.ServerConfig, error) {
serverLen := len(server.GetServerList())
if serverLen == 0 {
@ -475,9 +363,3 @@ func (server *NacosServer) GetNextServer() (constant.ServerConfig, error) {
index := atomic.AddInt32(&server.currentIndex, 1) % int32(serverLen)
return server.GetServerList()[index], nil
}
func (server *NacosServer) InjectSkAk(params map[string]string, clientConfig constant.ClientConfig) {
if clientConfig.AccessKey != "" {
params["Spas-AccessKey"] = clientConfig.AccessKey
}
}

View File

@ -18,9 +18,11 @@ package nacos_server
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
@ -61,18 +63,18 @@ func buildNacosServer(clientConfig constant.ClientConfig) (*NacosServer, error)
func TestNacosServer_InjectSignForNamingHttp_NoAk(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
AccessKey: "",
SecretKey: "",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string)
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSignForNamingHttp(param, constant.ClientConfig{})
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Empty(t, param["ak"])
assert.Empty(t, param["data"])
assert.Empty(t, param["signature"])
@ -88,10 +90,10 @@ func TestNacosServer_InjectSignForNamingHttp_WithGroup(t *testing.T) {
t.FailNow()
}
param := make(map[string]string)
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSignForNamingHttp(param, clientConfig)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.Contains(t, param["data"], "@@g-0@@s-0")
_, has := param["signature"]
@ -108,9 +110,9 @@ func TestNacosServer_InjectSignForNamingHttp_WithoutGroup(t *testing.T) {
t.FailNow()
}
param := make(map[string]string)
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
server.InjectSignForNamingHttp(param, clientConfig)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@g-0@@s-0")
assert.Contains(t, param["data"], "@@s-0")
@ -128,11 +130,11 @@ func TestNacosServer_InjectSignForNamingHttp_WithoutServiceName(t *testing.T) {
t.FailNow()
}
param := make(map[string]string)
param := make(map[string]string, 4)
param["groupName"] = "g-0"
server.InjectSignForNamingHttp(param, clientConfig)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@")
assert.Contains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
_, has := param["signature"]
assert.True(t, has)
@ -148,8 +150,8 @@ func TestNacosServer_InjectSignForNamingHttp_WithoutServiceNameAndGroup(t *testi
t.FailNow()
}
param := make(map[string]string)
server.InjectSignForNamingHttp(param, clientConfig)
param := make(map[string]string, 4)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["serviceName"], param["groupName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
@ -178,5 +180,8 @@ func TestNacosServer_UpdateServerListForSecurityLogin(t *testing.T) {
if err != nil {
t.FailNow()
}
assert.Equal(t, server.GetServerList(), server.securityLogin.GetServerList())
nacosAuthClient := server.securityLogin.Clients[0]
client, ok := nacosAuthClient.(*security.NacosAuthClient)
assert.True(t, ok)
assert.Equal(t, server.GetServerList(), client.GetServerList())
}

View File

@ -18,15 +18,16 @@ package rpc
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"time"
"github.com/golang/protobuf/ptypes/any"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
nacos_grpc_service "github.com/nacos-group/nacos-sdk-go/v2/api/grpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/grpc"
)
@ -86,7 +87,7 @@ func convertRequest(r rpc_request.IRequest) *nacos_grpc_service.Payload {
}
return &nacos_grpc_service.Payload{
Metadata: &Metadata,
Body: &any.Any{Value: []byte(r.GetBody(r))},
Body: &anypb.Any{Value: []byte(r.GetBody(r))},
}
}
@ -97,6 +98,6 @@ func convertResponse(r rpc_response.IResponse) *nacos_grpc_service.Payload {
}
return &nacos_grpc_service.Payload{
Metadata: &Metadata,
Body: &any.Any{Value: []byte(r.GetBody())},
Body: &anypb.Any{Value: []byte(r.GetBody())},
}
}

View File

@ -16,7 +16,10 @@
package rpc_request
import "github.com/nacos-group/nacos-sdk-go/v2/model"
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type ConfigRequest struct {
*Request
@ -67,7 +70,7 @@ func NewConfigBatchListenRequest(cacheLen int) *ConfigBatchListenRequest {
}
func (r *ConfigBatchListenRequest) GetRequestType() string {
return "ConfigBatchListenRequest"
return constant.CONFIG_BATCH_LISTEN_REQUEST_NAME
}
type ConfigChangeNotifyRequest struct {
@ -79,7 +82,7 @@ func NewConfigChangeNotifyRequest(group, dataId, tenant string) *ConfigChangeNot
}
func (r *ConfigChangeNotifyRequest) GetRequestType() string {
return "ConfigChangeNotifyRequest"
return constant.CONFIG_CHANGE_NOTIFY_REQUEST_NAME
}
type ConfigQueryRequest struct {
@ -92,7 +95,7 @@ func NewConfigQueryRequest(group, dataId, tenant string) *ConfigQueryRequest {
}
func (r *ConfigQueryRequest) GetRequestType() string {
return "ConfigQueryRequest"
return constant.CONFIG_QUERY_REQUEST_NAME
}
type ConfigPublishRequest struct {
@ -108,7 +111,7 @@ func NewConfigPublishRequest(group, dataId, tenant, content, casMd5 string) *Con
}
func (r *ConfigPublishRequest) GetRequestType() string {
return "ConfigPublishRequest"
return constant.CONFIG_PUBLISH_REQUEST_NAME
}
type ConfigRemoveRequest struct {
@ -120,5 +123,5 @@ func NewConfigRemoveRequest(group, dataId, tenant string) *ConfigRemoveRequest {
}
func (r *ConfigRemoveRequest) GetRequestType() string {
return "ConfigRemoveRequest"
return constant.CONFIG_REMOVE_REQUEST_NAME
}

View File

@ -21,6 +21,7 @@ import (
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
@ -68,7 +69,7 @@ func NewInstanceRequest(namespace, serviceName, groupName, Type string, instance
}
func (r *InstanceRequest) GetRequestType() string {
return "InstanceRequest"
return constant.INSTANCE_REQUEST_NAME
}
type BatchInstanceRequest struct {
@ -86,7 +87,7 @@ func NewBatchInstanceRequest(namespace, serviceName, groupName, Type string, ins
}
func (r *BatchInstanceRequest) GetRequestType() string {
return "BatchInstanceRequest"
return constant.BATCH_INSTANCE_REQUEST_NAME
}
type NotifySubscriberRequest struct {
@ -95,7 +96,7 @@ type NotifySubscriberRequest struct {
}
func (r *NotifySubscriberRequest) GetRequestType() string {
return "NotifySubscriberRequest"
return constant.NOTIFY_SUBSCRIBE_REQUEST_NAME
}
type ServiceListRequest struct {
@ -115,7 +116,7 @@ func NewServiceListRequest(namespace, serviceName, groupName string, pageNo, pag
}
func (r *ServiceListRequest) GetRequestType() string {
return "ServiceListRequest"
return constant.SERVICE_LIST_REQUEST_NAME
}
type SubscribeServiceRequest struct {
@ -133,7 +134,7 @@ func NewSubscribeServiceRequest(namespace, serviceName, groupName, clusters stri
}
func (r *SubscribeServiceRequest) GetRequestType() string {
return "SubscribeServiceRequest"
return constant.SUBSCRIBE_SERVICE_REQUEST_NAME
}
type ServiceQueryRequest struct {
@ -153,5 +154,5 @@ func NewServiceQueryRequest(namespace, serviceName, groupName, cluster string, h
}
func (r *ServiceQueryRequest) GetRequestType() string {
return "ServiceQueryRequest"
return constant.SERVICE_QUERY_REQUEST_NAME
}

View File

@ -0,0 +1,185 @@
package security
import (
"context"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/pkg/errors"
)
type NacosAuthClient struct {
username string
password string
accessToken *atomic.Value
tokenTtl int64
lastRefreshTime int64
tokenRefreshWindow int64
agent http_agent.IHttpAgent
clientCfg constant.ClientConfig
serverCfgs []constant.ServerConfig
}
func NewNacosAuthClient(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, agent http_agent.IHttpAgent) *NacosAuthClient {
client := &NacosAuthClient{
username: clientCfg.Username,
password: clientCfg.Password,
serverCfgs: serverCfgs,
clientCfg: clientCfg,
agent: agent,
accessToken: &atomic.Value{},
}
return client
}
func (ac *NacosAuthClient) GetAccessToken() string {
v := ac.accessToken.Load()
if v == nil {
return ""
}
return v.(string)
}
func (ac *NacosAuthClient) GetSecurityInfo(resource RequestResource) map[string]string {
var securityInfo = make(map[string]string, 4)
v := ac.accessToken.Load()
if v != nil {
securityInfo[constant.KEY_ACCESS_TOKEN] = v.(string)
}
return securityInfo
}
func (ac *NacosAuthClient) AutoRefresh(ctx context.Context) {
// If the username is not set, the automatic refresh Token is not enabled
if ac.username == "" {
return
}
go func() {
var timer *time.Timer
if lastLoginSuccess := ac.lastRefreshTime > 0 && ac.tokenTtl > 0 && ac.tokenRefreshWindow > 0; lastLoginSuccess {
timer = time.NewTimer(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
} else {
timer = time.NewTimer(time.Second * time.Duration(5))
}
defer timer.Stop()
for {
select {
case <-timer.C:
_, err := ac.Login()
if err != nil {
logger.Errorf("login has error %+v", err)
timer.Reset(time.Second * time.Duration(5))
} else {
logger.Infof("login success, tokenTtl: %+v seconds, tokenRefreshWindow: %+v seconds", ac.tokenTtl, ac.tokenRefreshWindow)
timer.Reset(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
}
case <-ctx.Done():
return
}
}
}()
}
func (ac *NacosAuthClient) Login() (bool, error) {
var throwable error = nil
for i := 0; i < len(ac.serverCfgs); i++ {
result, err := ac.login(ac.serverCfgs[i])
throwable = err
if result {
return true, nil
}
}
return false, throwable
}
func (ac *NacosAuthClient) UpdateServerList(serverList []constant.ServerConfig) {
ac.serverCfgs = serverList
}
func (ac *NacosAuthClient) GetServerList() []constant.ServerConfig {
return ac.serverCfgs
}
func (ac *NacosAuthClient) login(server constant.ServerConfig) (bool, error) {
if ac.lastRefreshTime > 0 && ac.tokenTtl > 0 {
// We refresh 2 windows before expiration to ensure continuous availability
tokenRefreshTime := ac.lastRefreshTime + ac.tokenTtl - 2*ac.tokenRefreshWindow
if time.Now().Unix() < tokenRefreshTime {
return true, nil
}
}
if ac.username == "" {
ac.lastRefreshTime = time.Now().Unix()
return true, nil
}
contextPath := server.ContextPath
if !strings.HasPrefix(contextPath, "/") {
contextPath = "/" + contextPath
}
if strings.HasSuffix(contextPath, "/") {
contextPath = contextPath[0 : len(contextPath)-1]
}
if server.Scheme == "" {
server.Scheme = "http"
}
reqUrl := server.Scheme + "://" + server.IpAddr + ":" + strconv.FormatInt(int64(server.Port), 10) + contextPath + "/v1/auth/users/login"
header := http.Header{
"content-type": []string{"application/x-www-form-urlencoded"},
}
resp, err := ac.agent.Post(reqUrl, header, ac.clientCfg.TimeoutMs, map[string]string{
"username": ac.username,
"password": ac.password,
})
if err != nil {
return false, err
}
var bytes []byte
bytes, err = io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return false, err
}
if resp.StatusCode != constant.RESPONSE_CODE_SUCCESS {
errMsg := string(bytes)
return false, errors.New(errMsg)
}
var result map[string]interface{}
err = json.Unmarshal(bytes, &result)
if err != nil {
return false, err
}
if val, ok := result[constant.KEY_ACCESS_TOKEN]; ok {
ac.accessToken.Store(val)
ac.lastRefreshTime = time.Now().Unix()
ac.tokenTtl = int64(result[constant.KEY_TOKEN_TTL].(float64))
ac.tokenRefreshWindow = ac.tokenTtl / 10
}
return true, nil
}

View File

@ -0,0 +1,289 @@
package security
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
// MockResponseBody creates a mock response body for testing
type MockResponseBody struct {
*bytes.Buffer
}
func (m *MockResponseBody) Close() error {
return nil
}
func NewMockResponseBody(data interface{}) io.ReadCloser {
var buf bytes.Buffer
if str, ok := data.(string); ok {
buf.WriteString(str)
} else {
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.Encode(data)
}
return &MockResponseBody{&buf}
}
// MockHttpAgent implements http_agent.IHttpAgent for testing
type MockHttpAgent struct {
PostFunc func(url string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error)
}
func (m *MockHttpAgent) Request(method string, url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
switch method {
case http.MethodPost:
return m.Post(url, header, timeoutMs, params)
default:
return &http.Response{
StatusCode: http.StatusMethodNotAllowed,
Body: NewMockResponseBody("method not allowed"),
}, nil
}
}
func (m *MockHttpAgent) RequestOnlyResult(method string, url string, header http.Header, timeoutMs uint64, params map[string]string) string {
resp, err := m.Request(method, url, header, timeoutMs, params)
if err != nil {
return ""
}
if resp.Body == nil {
return ""
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
return string(data)
}
func (m *MockHttpAgent) Get(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodGet, url, header, timeoutMs, params)
}
func (m *MockHttpAgent) Post(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
if m.PostFunc != nil {
return m.PostFunc(url, header, timeoutMs, params)
}
return &http.Response{
StatusCode: http.StatusNotImplemented,
Body: NewMockResponseBody("not implemented"),
}, nil
}
func (m *MockHttpAgent) Delete(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodDelete, url, header, timeoutMs, params)
}
func (m *MockHttpAgent) Put(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodPut, url, header, timeoutMs, params)
}
func TestNacosAuthClient_Login_Success(t *testing.T) {
// Setup mock response
mockResp := &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: "test-token",
constant.KEY_TOKEN_TTL: float64(10),
}),
}
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
// Verify request parameters
assert.Equal(t, "test-user", params["username"])
assert.Equal(t, "test-pass", params["password"])
contentType := header["content-type"]
assert.Equal(t, []string{"application/x-www-form-urlencoded"}, contentType)
return mockResp, nil
},
}
// Create client config
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
TimeoutMs: 10000,
}
serverConfigs := []constant.ServerConfig{
{
IpAddr: "127.0.0.1",
Port: 8848,
ContextPath: "/nacos",
},
}
client := NewNacosAuthClient(clientConfig, serverConfigs, mockAgent)
// Test login
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
// Verify token is stored
assert.Equal(t, "test-token", client.GetAccessToken())
}
func TestNacosAuthClient_Login_NoAuth(t *testing.T) {
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
t.Fatal("Should not make HTTP call when no username is set")
return nil, nil
},
}
clientConfig := constant.ClientConfig{}
serverConfigs := []constant.ServerConfig{{}}
client := NewNacosAuthClient(clientConfig, serverConfigs, mockAgent)
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Empty(t, client.GetAccessToken())
}
func TestNacosAuthClient_TokenRefresh(t *testing.T) {
callCount := 0
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
callCount++
return &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: "token-" + fmt.Sprintf("%d", callCount),
constant.KEY_TOKEN_TTL: float64(1), // 1 second TTL for quick testing
}),
}, nil
},
}
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
}
client := NewNacosAuthClient(clientConfig, []constant.ServerConfig{{IpAddr: "localhost"}}, mockAgent)
// Initial login
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Equal(t, "token-1", client.GetAccessToken())
// Wait for token to require refresh (1 second TTL)
time.Sleep(time.Second * 2)
// Second login should get new token
success, err = client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Equal(t, "token-2", client.GetAccessToken())
}
func TestNacosAuthClient_AutoRefresh(t *testing.T) {
callCount := 0
tokenChan := make(chan string, 2)
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
callCount++
token := fmt.Sprintf("auto-token-%d", callCount)
tokenChan <- token
t.Logf("Mock server received request #%d, returning token: %s", callCount, token)
return &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: token,
constant.KEY_TOKEN_TTL: float64(10), // 10 seconds TTL, resulting in 1s refresh window
}),
}, nil
},
}
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
}
client := NewNacosAuthClient(clientConfig, []constant.ServerConfig{{IpAddr: "localhost"}}, mockAgent)
// First do a manual login
t.Log("Performing initial manual login")
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
token1 := <-tokenChan // Get the token from the first login
t.Logf("Initial login successful, token: %s", token1)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// Start auto refresh
t.Log("Starting auto refresh")
client.AutoRefresh(ctx)
// Wait for token refresh (should happen after TTL-2*refreshWindow seconds = 8 seconds)
// We'll wait a bit longer to account for any delays
t.Log("Waiting for token refresh")
var token2 string
select {
case token2 = <-tokenChan:
t.Logf("Received refreshed token: %s", token2)
case <-time.After(time.Second * 12):
t.Fatal("Timeout waiting for token refresh")
}
assert.NotEqual(t, token1, token2, "Token should have been refreshed")
assert.Equal(t, "auto-token-1", token1, "First token should be auto-token-1")
assert.Equal(t, "auto-token-2", token2, "Second token should be auto-token-2")
}
func TestNacosAuthClient_GetSecurityInfo(t *testing.T) {
client := NewNacosAuthClient(constant.ClientConfig{}, []constant.ServerConfig{}, nil)
// When no token
info := client.GetSecurityInfo(RequestResource{})
assert.Empty(t, info[constant.KEY_ACCESS_TOKEN])
// When token exists
mockToken := "test-security-token"
client.accessToken.Store(mockToken)
info = client.GetSecurityInfo(RequestResource{})
assert.Equal(t, mockToken, info[constant.KEY_ACCESS_TOKEN])
}
func TestNacosAuthClient_LoginFailure(t *testing.T) {
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: NewMockResponseBody("Invalid credentials"),
}, nil
},
}
client := NewNacosAuthClient(
constant.ClientConfig{Username: "wrong-user", Password: "wrong-pass"},
[]constant.ServerConfig{{IpAddr: "localhost"}},
mockAgent,
)
success, err := client.Login()
assert.Error(t, err)
assert.False(t, success)
assert.Empty(t, client.GetAccessToken())
}

View File

@ -0,0 +1,95 @@
package security
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
type RamContext struct {
SignatureRegionId string
AccessKey string
SecretKey string
SecurityToken string
EphemeralAccessKeyId bool
}
type RamAuthClient struct {
clientConfig constant.ClientConfig
ramCredentialProviders []RamCredentialProvider
resourceInjector map[string]ResourceInjector
matchedProvider RamCredentialProvider
}
func NewRamAuthClient(clientCfg constant.ClientConfig) *RamAuthClient {
var providers = []RamCredentialProvider{
&RamRoleArnCredentialProvider{
clientConfig: clientCfg,
},
&EcsRamRoleCredentialProvider{
clientConfig: clientCfg,
},
&OIDCRoleArnCredentialProvider{
clientConfig: clientCfg,
},
&CredentialsURICredentialProvider{
clientConfig: clientCfg,
},
&AutoRotateCredentialProvider{
clientConfig: clientCfg,
},
&StsTokenCredentialProvider{
clientConfig: clientCfg,
},
&AccessKeyCredentialProvider{
clientConfig: clientCfg,
},
}
injectors := map[string]ResourceInjector{
REQUEST_TYPE_NAMING: &NamingResourceInjector{},
REQUEST_TYPE_CONFIG: &ConfigResourceInjector{},
}
return &RamAuthClient{
clientConfig: clientCfg,
ramCredentialProviders: providers,
resourceInjector: injectors,
}
}
func NewRamAuthClientWithProvider(clientCfg constant.ClientConfig, ramCredentialProvider RamCredentialProvider) *RamAuthClient {
ramAuthClient := NewRamAuthClient(clientCfg)
if ramCredentialProvider != nil {
ramAuthClient.ramCredentialProviders = append(ramAuthClient.ramCredentialProviders, ramCredentialProvider)
}
return ramAuthClient
}
func (rac *RamAuthClient) Login() (bool, error) {
for _, provider := range rac.ramCredentialProviders {
if provider.MatchProvider() {
rac.matchedProvider = provider
break
}
}
if rac.matchedProvider == nil {
return false, nil
}
err := rac.matchedProvider.Init()
if err != nil {
return false, err
}
return true, nil
}
func (rac *RamAuthClient) GetSecurityInfo(resource RequestResource) map[string]string {
var securityInfo = make(map[string]string, 4)
if rac.matchedProvider == nil {
return securityInfo
}
ramContext := rac.matchedProvider.GetCredentialsForNacosClient()
rac.resourceInjector[resource.requestType].doInject(resource, ramContext, securityInfo)
return securityInfo
}
func (rac *RamAuthClient) UpdateServerList(serverList []constant.ServerConfig) {
return
}

View File

@ -0,0 +1,380 @@
package security
import (
"encoding/json"
"os"
"strconv"
"github.com/aliyun/aliyun-secretsmanager-client-go/sdk"
"github.com/aliyun/credentials-go/credentials"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
const (
ENV_PREFIX string = "ALIBABA_CLOUD_"
ACCESS_KEY_ID_KEY string = ENV_PREFIX + "ACCESS_KEY_ID"
ACCESS_KEY_SECRET_KEY string = ENV_PREFIX + "ACCESS_KEY_SECRET"
SECURITY_TOKEN_KEY string = ENV_PREFIX + "SECURITY_TOKEN"
SIGNATURE_REGION_ID_KEY string = ENV_PREFIX + "SIGNATURE_REGION_ID"
RAM_ROLE_NAME_KEY string = ENV_PREFIX + "RAM_ROLE_NAME"
ROLE_ARN_KEY string = ENV_PREFIX + "ROLE_ARN"
ROLE_SESSION_NAME_KEY string = ENV_PREFIX + "ROLE_SESSION_NAME"
ROLE_SESSION_EXPIRATION_KEY string = ENV_PREFIX + "ROLE_SESSION_EXPIRATION"
POLICY_KEY string = ENV_PREFIX + "POLICY"
OIDC_PROVIDER_ARN_KEY string = ENV_PREFIX + "OIDC_PROVIDER_ARN"
OIDC_TOKEN_FILE_KEY string = ENV_PREFIX + "OIDC_TOKEN_FILE"
CREDENTIALS_URI_KEY string = ENV_PREFIX + "CREDENTIALS_URI"
SECRET_NAME_KEY string = ENV_PREFIX + "SECRET_NAME"
)
func GetNacosProperties(property string, envKey string) string {
if property != "" {
return property
} else {
return os.Getenv(envKey)
}
}
type RamCredentialProvider interface {
MatchProvider() bool
Init() error
GetCredentialsForNacosClient() RamContext
}
type AccessKeyCredentialProvider struct {
clientConfig constant.ClientConfig
accessKey string
secretKey string
signatureRegionId string
}
func (provider *AccessKeyCredentialProvider) MatchProvider() bool {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
return accessKey != "" && secretKey != ""
}
func (provider *AccessKeyCredentialProvider) Init() error {
provider.accessKey = GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
provider.secretKey = GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
if provider.clientConfig.RamConfig != nil {
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
} else {
provider.signatureRegionId = ""
}
return nil
}
func (provider *AccessKeyCredentialProvider) GetCredentialsForNacosClient() RamContext {
ramContext := RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SignatureRegionId: provider.signatureRegionId,
}
return ramContext
}
type AutoRotateCredentialProvider struct {
clientConfig constant.ClientConfig
secretManagerCacheClient *sdk.SecretManagerCacheClient
secretName string
signatureRegionId string
}
func (provider *AutoRotateCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
secretName := GetNacosProperties(provider.clientConfig.RamConfig.SecretName, SECRET_NAME_KEY)
return secretName != ""
}
func (provider *AutoRotateCredentialProvider) Init() error {
secretName := GetNacosProperties(provider.clientConfig.RamConfig.SecretName, SECRET_NAME_KEY)
client, err := sdk.NewClient()
if err != nil {
return err
}
provider.secretManagerCacheClient = client
provider.secretName = secretName
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *AutoRotateCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.secretManagerCacheClient == nil || provider.secretName == "" {
return RamContext{}
}
secretInfo, err := provider.secretManagerCacheClient.GetSecretInfo(provider.secretName)
if err != nil {
return RamContext{}
}
var m map[string]string
err = json.Unmarshal([]byte(secretInfo.SecretValue), &m)
if err != nil {
return RamContext{}
}
accessKeyId := m["AccessKeyId"]
accessKeySecret := m["AccessKeySecret"]
ramContext := RamContext{
AccessKey: accessKeyId,
SecretKey: accessKeySecret,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: false,
}
return ramContext
}
type StsTokenCredentialProvider struct {
clientConfig constant.ClientConfig
accessKey string
secretKey string
securityToken string
signatureRegionId string
}
func (provider *StsTokenCredentialProvider) MatchProvider() bool {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
if provider.clientConfig.RamConfig == nil {
return false
}
stsToken := GetNacosProperties(provider.clientConfig.RamConfig.SecurityToken, SECURITY_TOKEN_KEY)
return accessKey != "" && secretKey != "" && stsToken != ""
}
func (provider *StsTokenCredentialProvider) Init() error {
provider.accessKey = GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
provider.secretKey = GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
provider.securityToken = GetNacosProperties(provider.clientConfig.RamConfig.SecurityToken, SECURITY_TOKEN_KEY)
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *StsTokenCredentialProvider) GetCredentialsForNacosClient() RamContext {
ramContext := RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SecurityToken: provider.securityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
return ramContext
}
type EcsRamRoleCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *EcsRamRoleCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
ramRoleName := GetNacosProperties(provider.clientConfig.RamConfig.RamRoleName, RAM_ROLE_NAME_KEY)
return ramRoleName != ""
}
func (provider *EcsRamRoleCredentialProvider) Init() error {
ramRoleName := GetNacosProperties(provider.clientConfig.RamConfig.RamRoleName, RAM_ROLE_NAME_KEY)
credentialsConfig := new(credentials.Config).SetType("ecs_ram_role").SetRoleName(ramRoleName)
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *EcsRamRoleCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
ramContext := RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
return ramContext
}
type RamRoleArnCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *RamRoleArnCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
return accessKey == "" && secretKey == "" && roleArn != "" && roleSessionName != "" && oidcProviderArn == ""
}
func (provider *RamRoleArnCredentialProvider) Init() error {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
credentialsConfig := new(credentials.Config).SetType("ram_role_arn").
SetAccessKeyId(accessKey).SetAccessKeySecret(secretKey).
SetRoleArn(roleArn).SetRoleSessionName(roleSessionName)
if roleSessionExpiration := GetNacosProperties(strconv.Itoa(provider.clientConfig.RamConfig.RoleSessionExpiration), ROLE_SESSION_EXPIRATION_KEY); roleSessionExpiration != "" {
if roleSessionExpirationTime, err := strconv.Atoi(roleSessionExpiration); err == nil {
if roleSessionExpirationTime == 0 {
roleSessionExpirationTime = 3600
}
credentialsConfig.SetRoleSessionExpiration(roleSessionExpirationTime)
}
}
policy := GetNacosProperties(provider.clientConfig.RamConfig.Policy, POLICY_KEY)
if policy != "" {
credentialsConfig.SetPolicy(policy)
}
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *RamRoleArnCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}
type OIDCRoleArnCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *OIDCRoleArnCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
oidcTokenFile := GetNacosProperties(provider.clientConfig.RamConfig.OIDCTokenFilePath, OIDC_TOKEN_FILE_KEY)
return roleArn != "" && roleSessionName != "" && oidcProviderArn != "" && oidcTokenFile != ""
}
func (provider *OIDCRoleArnCredentialProvider) Init() error {
ramRoleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
oidcTokenFilePath := GetNacosProperties(provider.clientConfig.RamConfig.OIDCTokenFilePath, OIDC_TOKEN_FILE_KEY)
credentialsConfig := new(credentials.Config).SetType("oidc_role_arn").
SetRoleArn(ramRoleArn).SetRoleSessionName(roleSessionName).
SetOIDCProviderArn(oidcProviderArn).SetOIDCTokenFilePath(oidcTokenFilePath)
if roleSessionExpiration := GetNacosProperties(strconv.Itoa(provider.clientConfig.RamConfig.RoleSessionExpiration), ROLE_SESSION_EXPIRATION_KEY); roleSessionExpiration != "" {
if roleSessionExpirationTime, err := strconv.Atoi(roleSessionExpiration); err == nil {
if roleSessionExpirationTime == 0 {
roleSessionExpirationTime = 3600
}
credentialsConfig.SetRoleSessionExpiration(roleSessionExpirationTime)
}
}
policy := GetNacosProperties(provider.clientConfig.RamConfig.Policy, POLICY_KEY)
if policy != "" {
credentialsConfig.SetPolicy(policy)
}
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *OIDCRoleArnCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}
type CredentialsURICredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *CredentialsURICredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
credentialsURI := GetNacosProperties(provider.clientConfig.RamConfig.CredentialsURI, CREDENTIALS_URI_KEY)
return credentialsURI != ""
}
func (provider *CredentialsURICredentialProvider) Init() error {
credentialsURI := GetNacosProperties(provider.clientConfig.RamConfig.CredentialsURI, CREDENTIALS_URI_KEY)
credentialsConfig := new(credentials.Config).SetType("credentials_uri").SetURLCredential(credentialsURI)
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *CredentialsURICredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}

View File

@ -0,0 +1,127 @@
package security
import (
"fmt"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
type ResourceInjector interface {
doInject(resource RequestResource, ramContext RamContext, param map[string]string)
}
const (
CONFIG_AK_FILED string = "Spas-AccessKey"
NAMING_AK_FILED string = "ak"
SECURITY_TOKEN_HEADER string = "Spas-SecurityToken"
SIGNATURE_VERSION_HEADER string = "signatureVersion"
SIGNATURE_VERSION_V4 string = "v4"
SERVICE_INFO_SPLITER string = "@@"
TIMESTAMP_HEADER string = "Timestamp"
SIGNATURE_HEADER string = "Spas-Signature"
)
type NamingResourceInjector struct {
}
func (n *NamingResourceInjector) doInject(resource RequestResource, ramContext RamContext, param map[string]string) {
param[NAMING_AK_FILED] = ramContext.AccessKey
if ramContext.EphemeralAccessKeyId {
param[SECURITY_TOKEN_HEADER] = ramContext.SecurityToken
}
secretKey := trySignatureWithV4(ramContext, param)
signatures := n.calculateSignature(resource, secretKey, ramContext)
for k, v := range signatures {
param[k] = v
}
}
func (n *NamingResourceInjector) calculateSignature(resource RequestResource, secretKey string, ramContext RamContext) map[string]string {
var result = make(map[string]string, 4)
signData := n.getSignData(n.getGroupedServiceName(resource))
signature, err := Sign(signData, secretKey)
if err != nil {
logger.Errorf("get v4 signatrue error: %v", err)
return result
}
result["signature"] = signature
result["data"] = signData
return result
}
func (n *NamingResourceInjector) getGroupedServiceName(resource RequestResource) string {
if strings.Contains(resource.resource, SERVICE_INFO_SPLITER) || resource.group == "" {
return resource.resource
}
return resource.group + SERVICE_INFO_SPLITER + resource.resource
}
func (n *NamingResourceInjector) getSignData(serviceName string) string {
if serviceName != "" {
return fmt.Sprintf("%d%s%s", time.Now().UnixMilli(), SERVICE_INFO_SPLITER, serviceName)
}
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
type ConfigResourceInjector struct {
}
func (c *ConfigResourceInjector) doInject(resource RequestResource, ramContext RamContext, param map[string]string) {
param[CONFIG_AK_FILED] = ramContext.AccessKey
if ramContext.EphemeralAccessKeyId {
param[SECURITY_TOKEN_HEADER] = ramContext.SecurityToken
}
secretKey := trySignatureWithV4(ramContext, param)
signatures := c.calculateSignature(resource, secretKey, ramContext)
for k, v := range signatures {
param[k] = v
}
}
func (c *ConfigResourceInjector) calculateSignature(resource RequestResource, secretKey string, ramContext RamContext) map[string]string {
var result = make(map[string]string, 4)
resourceName := c.getResourceName(resource)
signHeaders := c.getSignHeaders(resourceName, secretKey)
for k, v := range signHeaders {
result[k] = v
}
return result
}
func (c *ConfigResourceInjector) getResourceName(resource RequestResource) string {
if resource.namespace != "" {
return resource.namespace + "+" + resource.group
} else {
return resource.group
}
}
func (c *ConfigResourceInjector) getSignHeaders(resource, secretKey string) map[string]string {
header := make(map[string]string, 4)
timeStamp := fmt.Sprintf("%d", time.Now().UnixMilli())
header[TIMESTAMP_HEADER] = timeStamp
if secretKey != "" {
var signature string
if strings.TrimSpace(resource) == "" {
signature = signWithHmacSha1Encrypt(timeStamp, secretKey)
} else {
signature = signWithHmacSha1Encrypt(resource+"+"+timeStamp, secretKey)
}
header[SIGNATURE_HEADER] = signature
}
return header
}
func trySignatureWithV4(ramContext RamContext, param map[string]string) string {
if ramContext.SignatureRegionId == "" {
return ramContext.SecretKey
}
signatureV4, err := finalSigningKeyStringWithDefaultInfo(ramContext.SecretKey, ramContext.SignatureRegionId)
if err != nil {
logger.Errorf("get v4 signatrue error: %v", err)
return ramContext.SecretKey
}
param[SIGNATURE_VERSION_HEADER] = SIGNATURE_VERSION_V4
return signatureV4
}

View File

@ -0,0 +1,98 @@
package security
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test_NamingResourceInjector_doInject(t *testing.T) {
namingResourceInjector := NamingResourceInjector{}
resource := BuildNamingResource("testNamespace", "testGroup", "testServiceName")
t.Run("test_doInject_v4_sts", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
SecurityToken: "testSecurityToken",
EphemeralAccessKeyId: true,
SignatureRegionId: "testSignatureRegionId",
}
param := map[string]string{}
namingResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[NAMING_AK_FILED], ramContext.AccessKey)
assert.Equal(t, param[SECURITY_TOKEN_HEADER], ramContext.SecurityToken)
assert.Equal(t, param[SIGNATURE_VERSION_HEADER], SIGNATURE_VERSION_V4)
assert.NotEmpty(t, param["signature"])
})
t.Run("test_doInject", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
}
param := map[string]string{}
namingResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[NAMING_AK_FILED], ramContext.AccessKey)
assert.Empty(t, param[SECURITY_TOKEN_HEADER])
assert.Empty(t, param[SIGNATURE_VERSION_HEADER])
assert.NotEmpty(t, param["signature"])
})
}
func Test_NamingResourceInjector_getGroupedServiceName(t *testing.T) {
namingResourceInjector := NamingResourceInjector{}
t.Run("test_getGroupedServiceName", func(t *testing.T) {
resource := BuildNamingResource("testNamespace", "testGroup", "testServiceName")
assert.Equal(t, namingResourceInjector.getGroupedServiceName(resource), "testGroup@@testServiceName")
})
t.Run("test_getGroupedServiceName_without_group", func(t *testing.T) {
resource := BuildNamingResource("testNamespace", "", "testServiceName")
assert.Equal(t, namingResourceInjector.getGroupedServiceName(resource), "testServiceName")
})
}
func Test_ConfigResourceInjector_doInject(t *testing.T) {
configResourceInjector := ConfigResourceInjector{}
resource := BuildConfigResource("testTenant", "testGroup", "testDataId")
t.Run("test_doInject_v4_sts", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
SecurityToken: "testSecurityToken",
EphemeralAccessKeyId: true,
SignatureRegionId: "testSignatureRegionId",
}
param := map[string]string{}
configResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[CONFIG_AK_FILED], ramContext.AccessKey)
assert.Equal(t, param[SECURITY_TOKEN_HEADER], ramContext.SecurityToken)
assert.Equal(t, param[SIGNATURE_VERSION_HEADER], SIGNATURE_VERSION_V4)
assert.NotEmpty(t, param[SIGNATURE_HEADER])
assert.NotEmpty(t, param[TIMESTAMP_HEADER])
})
t.Run("test_doInject", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
}
param := map[string]string{}
configResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[CONFIG_AK_FILED], ramContext.AccessKey)
assert.Empty(t, param[SECURITY_TOKEN_HEADER])
assert.Empty(t, param[SIGNATURE_VERSION_HEADER])
assert.NotEmpty(t, param[SIGNATURE_HEADER])
assert.NotEmpty(t, param[TIMESTAMP_HEADER])
})
}
func Test_ConfigResourceInjector_getResourceName(t *testing.T) {
configResourceInjector := ConfigResourceInjector{}
t.Run("test_getGroupedServiceName", func(t *testing.T) {
resource := BuildConfigResource("testTenant", "testGroup", "testDataId")
assert.Equal(t, configResourceInjector.getResourceName(resource), "testTenant+testGroup")
})
t.Run("test_getGroupedServiceName_without_group", func(t *testing.T) {
resource := BuildConfigResource("testTenant", "", "testDataId")
assert.Equal(t, configResourceInjector.getResourceName(resource), "testTenant+")
})
}

View File

@ -18,81 +18,135 @@ package security
import (
"context"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
)
type AuthClient struct {
username string
password string
accessToken *atomic.Value
tokenTtl int64
lastRefreshTime int64
tokenRefreshWindow int64
agent http_agent.IHttpAgent
clientCfg constant.ClientConfig
serverCfgs []constant.ServerConfig
type RequestResource struct {
requestType string
namespace string
group string
resource string
}
func NewAuthClient(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, agent http_agent.IHttpAgent) AuthClient {
client := AuthClient{
username: clientCfg.Username,
password: clientCfg.Password,
serverCfgs: serverCfgs,
clientCfg: clientCfg,
agent: agent,
accessToken: &atomic.Value{},
const (
REQUEST_TYPE_CONFIG = "config"
REQUEST_TYPE_NAMING = "naming"
)
func BuildConfigResourceByRequest(request rpc_request.IRequest) RequestResource {
if request.GetRequestType() == constant.CONFIG_QUERY_REQUEST_NAME {
configQueryRequest := request.(*rpc_request.ConfigQueryRequest)
return BuildConfigResource(configQueryRequest.Tenant, configQueryRequest.Group, configQueryRequest.DataId)
}
if request.GetRequestType() == constant.CONFIG_PUBLISH_REQUEST_NAME {
configPublishRequest := request.(*rpc_request.ConfigPublishRequest)
return BuildConfigResource(configPublishRequest.Tenant, configPublishRequest.Group, configPublishRequest.DataId)
}
if request.GetRequestType() == "ConfigRemoveRequest" {
configRemoveRequest := request.(*rpc_request.ConfigRemoveRequest)
return BuildConfigResource(configRemoveRequest.Tenant, configRemoveRequest.Group, configRemoveRequest.DataId)
}
return RequestResource{
requestType: REQUEST_TYPE_CONFIG,
}
}
return client
func BuildNamingResourceByRequest(request rpc_request.IRequest) RequestResource {
if request.GetRequestType() == constant.INSTANCE_REQUEST_NAME {
instanceRequest := request.(*rpc_request.InstanceRequest)
return BuildNamingResource(instanceRequest.Namespace, instanceRequest.GroupName, instanceRequest.ServiceName)
}
if request.GetRequestType() == constant.BATCH_INSTANCE_REQUEST_NAME {
batchInstanceRequest := request.(*rpc_request.BatchInstanceRequest)
return BuildNamingResource(batchInstanceRequest.Namespace, batchInstanceRequest.GroupName, batchInstanceRequest.ServiceName)
}
if request.GetRequestType() == constant.SERVICE_LIST_REQUEST_NAME {
serviceListRequest := request.(*rpc_request.ServiceListRequest)
return BuildNamingResource(serviceListRequest.Namespace, serviceListRequest.GroupName, serviceListRequest.ServiceName)
}
if request.GetRequestType() == constant.SERVICE_QUERY_REQUEST_NAME {
serviceQueryRequest := request.(*rpc_request.ServiceQueryRequest)
return BuildNamingResource(serviceQueryRequest.Namespace, serviceQueryRequest.GroupName, serviceQueryRequest.ServiceName)
}
if request.GetRequestType() == constant.SUBSCRIBE_SERVICE_REQUEST_NAME {
subscribeServiceRequest := request.(*rpc_request.SubscribeServiceRequest)
return BuildNamingResource(subscribeServiceRequest.Namespace, subscribeServiceRequest.GroupName, subscribeServiceRequest.ServiceName)
}
return RequestResource{
requestType: REQUEST_TYPE_NAMING,
}
}
func (ac *AuthClient) GetAccessToken() string {
v := ac.accessToken.Load()
if v == nil {
return ""
func BuildConfigResource(tenant, group, dataId string) RequestResource {
return RequestResource{
requestType: REQUEST_TYPE_CONFIG,
namespace: tenant,
group: group,
resource: dataId,
}
return v.(string)
}
func (ac *AuthClient) AutoRefresh(ctx context.Context) {
// If the username is not set, the automatic refresh Token is not enabled
if ac.username == "" {
return
func BuildNamingResource(namespace, group, serviceName string) RequestResource {
return RequestResource{
requestType: REQUEST_TYPE_NAMING,
namespace: namespace,
group: group,
resource: serviceName,
}
}
type AuthClient interface {
Login() (bool, error)
GetSecurityInfo(resource RequestResource) map[string]string
UpdateServerList(serverList []constant.ServerConfig)
}
type SecurityProxy struct {
Clients []AuthClient
}
func (sp *SecurityProxy) Login() {
for _, client := range sp.Clients {
_, err := client.Login()
if err != nil {
logger.Errorf("login in err:%v", err)
}
}
}
func (sp *SecurityProxy) GetSecurityInfo(resource RequestResource) map[string]string {
var securityInfo = make(map[string]string, 4)
for _, client := range sp.Clients {
info := client.GetSecurityInfo(resource)
if info != nil {
for k, v := range info {
securityInfo[k] = v
}
}
}
return securityInfo
}
func (sp *SecurityProxy) UpdateServerList(serverList []constant.ServerConfig) {
for _, client := range sp.Clients {
client.UpdateServerList(serverList)
}
}
func (sp *SecurityProxy) AutoRefresh(ctx context.Context) {
go func() {
var timer *time.Timer
if lastLoginSuccess := ac.lastRefreshTime > 0 && ac.tokenTtl > 0 && ac.tokenRefreshWindow > 0; lastLoginSuccess {
timer = time.NewTimer(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
} else {
timer = time.NewTimer(time.Second * time.Duration(5))
}
var timer = time.NewTimer(time.Second * time.Duration(5))
defer timer.Stop()
for {
select {
case <-timer.C:
_, err := ac.Login()
if err != nil {
logger.Errorf("login has error %+v", err)
sp.Login()
timer.Reset(time.Second * time.Duration(5))
} else {
logger.Infof("login success, tokenTtl: %+v seconds, tokenRefreshWindow: %+v seconds", ac.tokenTtl, ac.tokenRefreshWindow)
timer.Reset(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
}
case <-ctx.Done():
return
}
@ -100,83 +154,20 @@ func (ac *AuthClient) AutoRefresh(ctx context.Context) {
}()
}
func (ac *AuthClient) Login() (bool, error) {
var throwable error = nil
for i := 0; i < len(ac.serverCfgs); i++ {
result, err := ac.login(ac.serverCfgs[i])
throwable = err
if result {
return true, nil
func NewSecurityProxy(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, agent http_agent.IHttpAgent) SecurityProxy {
var securityProxy = SecurityProxy{}
securityProxy.Clients = []AuthClient{
NewNacosAuthClient(clientCfg, serverCfgs, agent),
NewRamAuthClient(clientCfg),
}
}
return false, throwable
return securityProxy
}
func (ac *AuthClient) UpdateServerList(serverList []constant.ServerConfig) {
ac.serverCfgs = serverList
func NewSecurityProxyWithRamCredentialProvider(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, agent http_agent.IHttpAgent, provider RamCredentialProvider) SecurityProxy {
var securityProxy = SecurityProxy{}
securityProxy.Clients = []AuthClient{
NewNacosAuthClient(clientCfg, serverCfgs, agent),
NewRamAuthClientWithProvider(clientCfg, provider),
}
func (ac *AuthClient) GetServerList() []constant.ServerConfig {
return ac.serverCfgs
}
func (ac *AuthClient) login(server constant.ServerConfig) (bool, error) {
if ac.username != "" {
contextPath := server.ContextPath
if !strings.HasPrefix(contextPath, "/") {
contextPath = "/" + contextPath
}
if strings.HasSuffix(contextPath, "/") {
contextPath = contextPath[0 : len(contextPath)-1]
}
if server.Scheme == "" {
server.Scheme = "http"
}
reqUrl := server.Scheme + "://" + server.IpAddr + ":" + strconv.FormatInt(int64(server.Port), 10) + contextPath + "/v1/auth/users/login"
header := http.Header{
"content-type": []string{"application/x-www-form-urlencoded"},
}
resp, err := ac.agent.Post(reqUrl, header, ac.clientCfg.TimeoutMs, map[string]string{
"username": ac.username,
"password": ac.password,
})
if err != nil {
return false, err
}
var bytes []byte
bytes, err = io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return false, err
}
if resp.StatusCode != constant.RESPONSE_CODE_SUCCESS {
errMsg := string(bytes)
return false, errors.New(errMsg)
}
var result map[string]interface{}
err = json.Unmarshal(bytes, &result)
if err != nil {
return false, err
}
if val, ok := result[constant.KEY_ACCESS_TOKEN]; ok {
ac.accessToken.Store(val)
ac.lastRefreshTime = time.Now().Unix()
ac.tokenTtl = int64(result[constant.KEY_TOKEN_TTL].(float64))
ac.tokenRefreshWindow = ac.tokenTtl / 10
}
}
return true, nil
return securityProxy
}

View File

@ -0,0 +1,97 @@
package security
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
)
const (
PREFIX = "aliyun_v4"
CONSTANT = "aliyun_v4_request"
V4_SIGN_DATE_FORMATTER = "20060102"
SIGNATURE_V4_PRODUCE = "mse"
)
func signWithHmacSha1Encrypt(encryptText, encryptKey string) string {
key := []byte(encryptKey)
mac := hmac.New(sha1.New, key)
mac.Write([]byte(encryptText))
expectedMAC := mac.Sum(nil)
return base64.StdEncoding.EncodeToString(expectedMAC)
}
func Sign(data, key string) (string, error) {
signature, err := sign([]byte(data), []byte(key))
if err != nil {
return "", fmt.Errorf("unable to calculate a request signature: %w", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// sign 方法用于生成签名字节数组
func sign(data, key []byte) ([]byte, error) {
mac := hmac.New(sha1.New, key)
if _, err := mac.Write(data); err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
func finalSigningKeyStringWithDefaultInfo(secret, region string) (string, error) {
signDate := time.Now().UTC().Format(V4_SIGN_DATE_FORMATTER)
return finalSigningKeyString(secret, signDate, region, SIGNATURE_V4_PRODUCE)
}
func finalSigningKeyString(secret, date, region, productCode string) (string, error) {
finalKey, err := finalSigningKey(secret, date, region, productCode)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(finalKey), nil
}
func finalSigningKey(secret, date, region, productCode string) ([]byte, error) {
secondSignkey, err := regionSigningKey(secret, date, region)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, secondSignkey)
_, err = mac.Write([]byte(productCode))
if err != nil {
return nil, err
}
thirdSigningKey := mac.Sum(nil)
mac = hmac.New(sha256.New, thirdSigningKey)
_, err = mac.Write([]byte(CONSTANT))
if err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
func regionSigningKey(secret, date, region string) ([]byte, error) {
firstSignkey, err := firstSigningKey(secret, date)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, firstSignkey)
_, err = mac.Write([]byte(region))
if err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
func firstSigningKey(secret, date string) ([]byte, error) {
mac := hmac.New(sha256.New, []byte(PREFIX+secret))
_, err := mac.Write([]byte(date))
if err != nil {
return nil, err
}
return mac.Sum(nil), nil
}

23
go.mod
View File

@ -1,6 +1,7 @@
module github.com/nacos-group/nacos-sdk-go/v2
go 1.18
// v2.2.9 is deprecated due to a critical bug. Use v2.3.0 or later
go 1.21
require (
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
@ -8,22 +9,26 @@ require (
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5
github.com/aliyun/credentials-go v1.4.3
github.com/buger/jsonparser v1.1.1
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.3
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.2
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.21.0
golang.org/x/sync v0.10.0
golang.org/x/time v0.1.0
google.golang.org/grpc v1.56.3
google.golang.org/grpc v1.67.3
google.golang.org/protobuf v1.36.5
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
require github.com/golang/protobuf v1.5.4 // indirect
require (
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
@ -37,11 +42,11 @@ require (
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.1.0 // indirect
@ -49,6 +54,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
@ -60,8 +66,7 @@ require (
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

42
go.sum
View File

@ -79,6 +79,7 @@ github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZL
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o=
github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
@ -87,15 +88,18 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 h1:rWkH6D2XlXb/Y+tNAQROxBzp3a0p92ni+pXcaHBe/WI=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2/go.mod h1:GDtq+Kw+v0fO+j5BrrWiUHbBq7L+hfpzpPfXKOZMFE0=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 h1:olLiPI2iM8Hqq6vKnSxpM3awCrm9/BeOgHpzQkOYnI4=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7/go.mod h1:oDg1j4kFxnhgftaiLJABkGeSvuEvSF5Lo6UmRAMruX4=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 h1:nJYyoFP+aqGKgPs9JeZgS1rWQ4NndNR0Zfhh161ZltU=
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1/go.mod h1:WzGOmFFTlUzXM03CJnHWMQ85UN6QGpOXZocCjwkiyOg=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 h1:QeUdR7JF7iNCvO/81EhxEr3wDwxk4YBoYZOq6E0AjHI=
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8/go.mod h1:xP0KIZry6i7oGPF24vhAPr1Q8vLZRcMcxtft5xDKwCU=
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 h1:8S0mtD101RDYa0LXwdoqgN0RxdMmmJYjq8g2mk7/lQ4=
github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5/go.mod h1:M19fxYz3gpm0ETnoKweYyYtqrtnVtrpKFpwsghbw+cQ=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/aliyun/credentials-go v1.4.3 h1:N3iHyvHRMyOwY1+0qBLSf3hb5JFiOujVSVuEpgeGttY=
github.com/aliyun/credentials-go v1.4.3/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -107,8 +111,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -119,6 +123,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -164,8 +170,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -177,7 +183,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -231,6 +238,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc h1:Ak86L+yDSOzKFa7WM5bf5itSOo1e3Xh8bm5YCMUXIjQ=
github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -596,8 +605,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -610,8 +619,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -624,8 +633,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -70,11 +70,14 @@ func (u UUID) MarshalText() (text []byte, err error) {
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Following formats are supported:
//
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
// "6ba7b8109dad11d180b400c04fd430c8"
//
// ABNF for supported UUID text representation follows:
//
// uuid := canonical | hashlike | braced | urn
// plain := canonical | hashlike
// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct

View File

@ -152,6 +152,7 @@ func (u *UUID) SetVariant(v byte) {
// Must is a helper that wraps a call to a function returning (UUID, error)
// and panics if the error is non-nil. It is intended for use in variable
// initializations such as
//
// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000"));
func Must(u UUID, err error) UUID {
if err != nil {

View File

@ -46,3 +46,9 @@ type ConfigContext struct {
DataId string `json:"dataId"`
Tenant string `json:"tenant"`
}
type ConfigPageResult struct {
Code int `json:"code"`
Message string `json:"message"`
Data ConfigPage `json:"data"`
}

View File

@ -139,3 +139,12 @@ func DeepCopyMap(params map[string]string) map[string]string {
}
return result
}
func Contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}

View File

@ -16,9 +16,13 @@
package vo
import "github.com/nacos-group/nacos-sdk-go/v2/common/constant"
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
)
type NacosClientParam struct {
ClientConfig *constant.ClientConfig // optional
ServerConfigs []constant.ServerConfig // optional
RamCredentialProvider security.RamCredentialProvider // optinal
}

View File

@ -23,6 +23,7 @@ type ConfigParam struct {
Group string `param:"group"` //required
Content string `param:"content"` //required
Tag string `param:"tag"`
ConfigTags string `param:"configTags"`
AppName string `param:"appName"`
BetaIps string `param:"betaIps"`
CasMd5 string `param:"casMd5"`