From d794f60a4dcd380784edec7d04ea8b8140381583 Mon Sep 17 00:00:00 2001 From: Kyriakos Akriotis Date: Mon, 5 Oct 2026 07:30:11 +0200 Subject: [PATCH 1/4] added remote url to plugin metadata, added proto regeneration script #93 --- .idea/o7k.iml | 1 + pluginsdk/plugin.go | 1 + pluginsdk/proto/plugin.pb.go | 262 +++++++++++++----------------- pluginsdk/proto/plugin.proto | 4 +- pluginsdk/proto/plugin_grpc.pb.go | 6 +- scripts/generate-proto.sh | 12 ++ 6 files changed, 134 insertions(+), 152 deletions(-) create mode 100755 scripts/generate-proto.sh diff --git a/.idea/o7k.iml b/.idea/o7k.iml index 7c421e3..9c408cc 100644 --- a/.idea/o7k.iml +++ b/.idea/o7k.iml @@ -1,5 +1,6 @@ + diff --git a/pluginsdk/plugin.go b/pluginsdk/plugin.go index f74fc0b..2d0c7b3 100644 --- a/pluginsdk/plugin.go +++ b/pluginsdk/plugin.go @@ -20,6 +20,7 @@ type Metadata struct { Name string Version string Color string + URL string } type Context struct { diff --git a/pluginsdk/proto/plugin.pb.go b/pluginsdk/proto/plugin.pb.go index 207433b..019b413 100644 --- a/pluginsdk/proto/plugin.pb.go +++ b/pluginsdk/proto/plugin.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.36.12 // protoc v5.29.3 -// source: plugin.proto +// source: pluginsdk/proto/plugin.proto package proto @@ -29,7 +29,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - mi := &file_plugin_proto_msgTypes[0] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -41,7 +41,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[0] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -54,7 +54,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{0} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{0} } type InitializeRequest struct { @@ -66,7 +66,7 @@ type InitializeRequest struct { func (x *InitializeRequest) Reset() { *x = InitializeRequest{} - mi := &file_plugin_proto_msgTypes[1] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -78,7 +78,7 @@ func (x *InitializeRequest) String() string { func (*InitializeRequest) ProtoMessage() {} func (x *InitializeRequest) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[1] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -91,7 +91,7 @@ func (x *InitializeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InitializeRequest.ProtoReflect.Descriptor instead. func (*InitializeRequest) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{1} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{1} } func (x *InitializeRequest) GetHostBrokerId() uint32 { @@ -106,13 +106,14 @@ type Metadata struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` Color string `protobuf:"bytes,3,opt,name=color,proto3" json:"color,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Metadata) Reset() { *x = Metadata{} - mi := &file_plugin_proto_msgTypes[2] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -124,7 +125,7 @@ func (x *Metadata) String() string { func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[2] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -137,7 +138,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Metadata.ProtoReflect.Descriptor instead. func (*Metadata) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{2} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{2} } func (x *Metadata) GetName() string { @@ -161,6 +162,13 @@ func (x *Metadata) GetColor() string { return "" } +func (x *Metadata) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + type Context struct { state protoimpl.MessageState `protogen:"open.v1"` Generation uint64 `protobuf:"varint,1,opt,name=generation,proto3" json:"generation,omitempty"` @@ -173,7 +181,7 @@ type Context struct { func (x *Context) Reset() { *x = Context{} - mi := &file_plugin_proto_msgTypes[3] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -185,7 +193,7 @@ func (x *Context) String() string { func (*Context) ProtoMessage() {} func (x *Context) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[3] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -198,7 +206,7 @@ func (x *Context) ProtoReflect() protoreflect.Message { // Deprecated: Use Context.ProtoReflect.Descriptor instead. func (*Context) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{3} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{3} } func (x *Context) GetGeneration() uint64 { @@ -238,7 +246,7 @@ type Resources struct { func (x *Resources) Reset() { *x = Resources{} - mi := &file_plugin_proto_msgTypes[4] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -250,7 +258,7 @@ func (x *Resources) String() string { func (*Resources) ProtoMessage() {} func (x *Resources) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[4] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -263,7 +271,7 @@ func (x *Resources) ProtoReflect() protoreflect.Message { // Deprecated: Use Resources.ProtoReflect.Descriptor instead. func (*Resources) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{4} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{4} } func (x *Resources) GetResources() []*Resource { @@ -287,7 +295,7 @@ type Resource struct { func (x *Resource) Reset() { *x = Resource{} - mi := &file_plugin_proto_msgTypes[5] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -299,7 +307,7 @@ func (x *Resource) String() string { func (*Resource) ProtoMessage() {} func (x *Resource) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[5] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -312,7 +320,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource.ProtoReflect.Descriptor instead. func (*Resource) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{5} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{5} } func (x *Resource) GetService() string { @@ -369,7 +377,7 @@ type Column struct { func (x *Column) Reset() { *x = Column{} - mi := &file_plugin_proto_msgTypes[6] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -381,7 +389,7 @@ func (x *Column) String() string { func (*Column) ProtoMessage() {} func (x *Column) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[6] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -394,7 +402,7 @@ func (x *Column) ProtoReflect() protoreflect.Message { // Deprecated: Use Column.ProtoReflect.Descriptor instead. func (*Column) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{6} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{6} } func (x *Column) GetKey() string { @@ -430,14 +438,13 @@ type Command struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` Default bool `protobuf:"varint,3,opt,name=default,proto3" json:"default,omitempty"` - StatusLabel string `protobuf:"bytes,4,opt,name=status_label,json=statusLabel,proto3" json:"status_label,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Command) Reset() { *x = Command{} - mi := &file_plugin_proto_msgTypes[7] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -449,7 +456,7 @@ func (x *Command) String() string { func (*Command) ProtoMessage() {} func (x *Command) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[7] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -462,7 +469,7 @@ func (x *Command) ProtoReflect() protoreflect.Message { // Deprecated: Use Command.ProtoReflect.Descriptor instead. func (*Command) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{7} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{7} } func (x *Command) GetKey() string { @@ -486,13 +493,6 @@ func (x *Command) GetDefault() bool { return false } -func (x *Command) GetStatusLabel() string { - if x != nil { - return x.StatusLabel - } - return "" -} - type Row struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -503,7 +503,7 @@ type Row struct { func (x *Row) Reset() { *x = Row{} - mi := &file_plugin_proto_msgTypes[8] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -515,7 +515,7 @@ func (x *Row) String() string { func (*Row) ProtoMessage() {} func (x *Row) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[8] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -528,7 +528,7 @@ func (x *Row) ProtoReflect() protoreflect.Message { // Deprecated: Use Row.ProtoReflect.Descriptor instead. func (*Row) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{8} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{8} } func (x *Row) GetId() string { @@ -548,14 +548,13 @@ func (x *Row) GetFields() map[string]string { type ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` - Scope map[string]string `protobuf:"bytes,2,rep,name=scope,proto3" json:"scope,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { *x = ListRequest{} - mi := &file_plugin_proto_msgTypes[9] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -567,7 +566,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[9] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -580,7 +579,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{9} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{9} } func (x *ListRequest) GetResource() string { @@ -590,13 +589,6 @@ func (x *ListRequest) GetResource() string { return "" } -func (x *ListRequest) GetScope() map[string]string { - if x != nil { - return x.Scope - } - return nil -} - type ListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Rows []*Row `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` @@ -606,7 +598,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_plugin_proto_msgTypes[10] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -618,7 +610,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[10] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -631,7 +623,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{10} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{10} } func (x *ListResponse) GetRows() []*Row { @@ -646,14 +638,13 @@ type ExecuteRequest struct { Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` Command *Command `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` Row *Row `protobuf:"bytes,3,opt,name=row,proto3" json:"row,omitempty"` - Scope map[string]string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ExecuteRequest) Reset() { *x = ExecuteRequest{} - mi := &file_plugin_proto_msgTypes[11] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -665,7 +656,7 @@ func (x *ExecuteRequest) String() string { func (*ExecuteRequest) ProtoMessage() {} func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[11] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -678,7 +669,7 @@ func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteRequest.ProtoReflect.Descriptor instead. func (*ExecuteRequest) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{11} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{11} } func (x *ExecuteRequest) GetResource() string { @@ -702,13 +693,6 @@ func (x *ExecuteRequest) GetRow() *Row { return nil } -func (x *ExecuteRequest) GetScope() map[string]string { - if x != nil { - return x.Scope - } - return nil -} - type ExecuteResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Result *Result `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` @@ -718,7 +702,7 @@ type ExecuteResponse struct { func (x *ExecuteResponse) Reset() { *x = ExecuteResponse{} - mi := &file_plugin_proto_msgTypes[12] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +714,7 @@ func (x *ExecuteResponse) String() string { func (*ExecuteResponse) ProtoMessage() {} func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[12] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +727,7 @@ func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. func (*ExecuteResponse) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{12} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{12} } func (x *ExecuteResponse) GetResult() *Result { @@ -763,7 +747,7 @@ type Result struct { func (x *Result) Reset() { *x = Result{} - mi := &file_plugin_proto_msgTypes[13] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -775,7 +759,7 @@ func (x *Result) String() string { func (*Result) ProtoMessage() {} func (x *Result) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[13] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -788,7 +772,7 @@ func (x *Result) ProtoReflect() protoreflect.Message { // Deprecated: Use Result.ProtoReflect.Descriptor instead. func (*Result) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{13} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{13} } func (x *Result) GetDetails() *Details { @@ -815,7 +799,7 @@ type Details struct { func (x *Details) Reset() { *x = Details{} - mi := &file_plugin_proto_msgTypes[14] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -827,7 +811,7 @@ func (x *Details) String() string { func (*Details) ProtoMessage() {} func (x *Details) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[14] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -840,7 +824,7 @@ func (x *Details) ProtoReflect() protoreflect.Message { // Deprecated: Use Details.ProtoReflect.Descriptor instead. func (*Details) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{14} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{14} } func (x *Details) GetId() string { @@ -870,7 +854,7 @@ type Navigate struct { func (x *Navigate) Reset() { *x = Navigate{} - mi := &file_plugin_proto_msgTypes[15] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -882,7 +866,7 @@ func (x *Navigate) String() string { func (*Navigate) ProtoMessage() {} func (x *Navigate) ProtoReflect() protoreflect.Message { - mi := &file_plugin_proto_msgTypes[15] + mi := &file_pluginsdk_proto_plugin_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -895,7 +879,7 @@ func (x *Navigate) ProtoReflect() protoreflect.Message { // Deprecated: Use Navigate.ProtoReflect.Descriptor instead. func (*Navigate) Descriptor() ([]byte, []int) { - return file_plugin_proto_rawDescGZIP(), []int{15} + return file_pluginsdk_proto_plugin_proto_rawDescGZIP(), []int{15} } func (x *Navigate) GetResource() string { @@ -933,18 +917,19 @@ func (x *Navigate) GetScope() map[string]string { return nil } -var File_plugin_proto protoreflect.FileDescriptor +var File_pluginsdk_proto_plugin_proto protoreflect.FileDescriptor -const file_plugin_proto_rawDesc = "" + +const file_pluginsdk_proto_plugin_proto_rawDesc = "" + "\n" + - "\fplugin.proto\x12\ro7k.plugin.v1\"\a\n" + + "\x1cpluginsdk/proto/plugin.proto\x12\ro7k.plugin.v1\"\a\n" + "\x05Empty\"9\n" + "\x11InitializeRequest\x12$\n" + - "\x0ehost_broker_id\x18\x01 \x01(\rR\fhostBrokerId\"N\n" + + "\x0ehost_broker_id\x18\x01 \x01(\rR\fhostBrokerId\"`\n" + "\bMetadata\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x14\n" + - "\x05color\x18\x03 \x01(\tR\x05color\"x\n" + + "\x05color\x18\x03 \x01(\tR\x05color\x12\x10\n" + + "\x03url\x18\x04 \x01(\tR\x03url\"x\n" + "\aContext\x12\x1e\n" + "\n" + "generation\x18\x01 \x01(\x04R\n" + @@ -966,36 +951,25 @@ const file_plugin_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05title\x18\x02 \x01(\tR\x05title\x12\x1b\n" + "\tmin_width\x18\x03 \x01(\x05R\bminWidth\x12\x12\n" + - "\x04flex\x18\x04 \x01(\x05R\x04flex\"z\n" + + "\x04flex\x18\x04 \x01(\x05R\x04flex\"W\n" + "\aCommand\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x18\n" + - "\adefault\x18\x03 \x01(\bR\adefault\x12!\n" + - "\fstatus_label\x18\x04 \x01(\tR\vstatusLabel\"\x88\x01\n" + + "\adefault\x18\x03 \x01(\bR\adefault\"\x88\x01\n" + "\x03Row\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x126\n" + "\x06fields\x18\x02 \x03(\v2\x1e.o7k.plugin.v1.Row.FieldsEntryR\x06fields\x1a9\n" + "\vFieldsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa0\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\")\n" + "\vListRequest\x12\x1a\n" + - "\bresource\x18\x01 \x01(\tR\bresource\x12;\n" + - "\x05scope\x18\x02 \x03(\v2%.o7k.plugin.v1.ListRequest.ScopeEntryR\x05scope\x1a8\n" + - "\n" + - "ScopeEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"6\n" + + "\bresource\x18\x01 \x01(\tR\bresource\"6\n" + "\fListResponse\x12&\n" + - "\x04rows\x18\x01 \x03(\v2\x12.o7k.plugin.v1.RowR\x04rows\"\xfe\x01\n" + + "\x04rows\x18\x01 \x03(\v2\x12.o7k.plugin.v1.RowR\x04rows\"\x84\x01\n" + "\x0eExecuteRequest\x12\x1a\n" + "\bresource\x18\x01 \x01(\tR\bresource\x120\n" + "\acommand\x18\x02 \x01(\v2\x16.o7k.plugin.v1.CommandR\acommand\x12$\n" + - "\x03row\x18\x03 \x01(\v2\x12.o7k.plugin.v1.RowR\x03row\x12>\n" + - "\x05scope\x18\x04 \x03(\v2(.o7k.plugin.v1.ExecuteRequest.ScopeEntryR\x05scope\x1a8\n" + - "\n" + - "ScopeEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"@\n" + + "\x03row\x18\x03 \x01(\v2\x12.o7k.plugin.v1.RowR\x03row\"@\n" + "\x0fExecuteResponse\x12-\n" + "\x06result\x18\x01 \x01(\v2\x15.o7k.plugin.v1.ResultR\x06result\"o\n" + "\x06Result\x120\n" + @@ -1026,19 +1000,19 @@ const file_plugin_proto_rawDesc = "" + "GetContext\x12\x14.o7k.plugin.v1.Empty\x1a\x16.o7k.plugin.v1.ContextB/Z-github.com/akyriako/o7k/pluginsdk/proto;protob\x06proto3" var ( - file_plugin_proto_rawDescOnce sync.Once - file_plugin_proto_rawDescData []byte + file_pluginsdk_proto_plugin_proto_rawDescOnce sync.Once + file_pluginsdk_proto_plugin_proto_rawDescData []byte ) -func file_plugin_proto_rawDescGZIP() []byte { - file_plugin_proto_rawDescOnce.Do(func() { - file_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_plugin_proto_rawDesc), len(file_plugin_proto_rawDesc))) +func file_pluginsdk_proto_plugin_proto_rawDescGZIP() []byte { + file_pluginsdk_proto_plugin_proto_rawDescOnce.Do(func() { + file_pluginsdk_proto_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_pluginsdk_proto_plugin_proto_rawDesc), len(file_pluginsdk_proto_plugin_proto_rawDesc))) }) - return file_plugin_proto_rawDescData + return file_pluginsdk_proto_plugin_proto_rawDescData } -var file_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 20) -var file_plugin_proto_goTypes = []any{ +var file_pluginsdk_proto_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_pluginsdk_proto_plugin_proto_goTypes = []any{ (*Empty)(nil), // 0: o7k.plugin.v1.Empty (*InitializeRequest)(nil), // 1: o7k.plugin.v1.InitializeRequest (*Metadata)(nil), // 2: o7k.plugin.v1.Metadata @@ -1056,63 +1030,59 @@ var file_plugin_proto_goTypes = []any{ (*Details)(nil), // 14: o7k.plugin.v1.Details (*Navigate)(nil), // 15: o7k.plugin.v1.Navigate nil, // 16: o7k.plugin.v1.Row.FieldsEntry - nil, // 17: o7k.plugin.v1.ListRequest.ScopeEntry - nil, // 18: o7k.plugin.v1.ExecuteRequest.ScopeEntry - nil, // 19: o7k.plugin.v1.Navigate.ScopeEntry + nil, // 17: o7k.plugin.v1.Navigate.ScopeEntry } -var file_plugin_proto_depIdxs = []int32{ +var file_pluginsdk_proto_plugin_proto_depIdxs = []int32{ 5, // 0: o7k.plugin.v1.Resources.resources:type_name -> o7k.plugin.v1.Resource 6, // 1: o7k.plugin.v1.Resource.columns:type_name -> o7k.plugin.v1.Column 7, // 2: o7k.plugin.v1.Resource.commands:type_name -> o7k.plugin.v1.Command 16, // 3: o7k.plugin.v1.Row.fields:type_name -> o7k.plugin.v1.Row.FieldsEntry - 17, // 4: o7k.plugin.v1.ListRequest.scope:type_name -> o7k.plugin.v1.ListRequest.ScopeEntry - 8, // 5: o7k.plugin.v1.ListResponse.rows:type_name -> o7k.plugin.v1.Row - 7, // 6: o7k.plugin.v1.ExecuteRequest.command:type_name -> o7k.plugin.v1.Command - 8, // 7: o7k.plugin.v1.ExecuteRequest.row:type_name -> o7k.plugin.v1.Row - 18, // 8: o7k.plugin.v1.ExecuteRequest.scope:type_name -> o7k.plugin.v1.ExecuteRequest.ScopeEntry - 13, // 9: o7k.plugin.v1.ExecuteResponse.result:type_name -> o7k.plugin.v1.Result - 14, // 10: o7k.plugin.v1.Result.details:type_name -> o7k.plugin.v1.Details - 15, // 11: o7k.plugin.v1.Result.navigate:type_name -> o7k.plugin.v1.Navigate - 19, // 12: o7k.plugin.v1.Navigate.scope:type_name -> o7k.plugin.v1.Navigate.ScopeEntry - 1, // 13: o7k.plugin.v1.Plugin.Initialize:input_type -> o7k.plugin.v1.InitializeRequest - 0, // 14: o7k.plugin.v1.Plugin.GetMetadata:input_type -> o7k.plugin.v1.Empty - 0, // 15: o7k.plugin.v1.Plugin.GetResources:input_type -> o7k.plugin.v1.Empty - 9, // 16: o7k.plugin.v1.Plugin.List:input_type -> o7k.plugin.v1.ListRequest - 11, // 17: o7k.plugin.v1.Plugin.Execute:input_type -> o7k.plugin.v1.ExecuteRequest - 0, // 18: o7k.plugin.v1.Host.GetContext:input_type -> o7k.plugin.v1.Empty - 0, // 19: o7k.plugin.v1.Plugin.Initialize:output_type -> o7k.plugin.v1.Empty - 2, // 20: o7k.plugin.v1.Plugin.GetMetadata:output_type -> o7k.plugin.v1.Metadata - 4, // 21: o7k.plugin.v1.Plugin.GetResources:output_type -> o7k.plugin.v1.Resources - 10, // 22: o7k.plugin.v1.Plugin.List:output_type -> o7k.plugin.v1.ListResponse - 12, // 23: o7k.plugin.v1.Plugin.Execute:output_type -> o7k.plugin.v1.ExecuteResponse - 3, // 24: o7k.plugin.v1.Host.GetContext:output_type -> o7k.plugin.v1.Context - 19, // [19:25] is the sub-list for method output_type - 13, // [13:19] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name -} - -func init() { file_plugin_proto_init() } -func file_plugin_proto_init() { - if File_plugin_proto != nil { + 8, // 4: o7k.plugin.v1.ListResponse.rows:type_name -> o7k.plugin.v1.Row + 7, // 5: o7k.plugin.v1.ExecuteRequest.command:type_name -> o7k.plugin.v1.Command + 8, // 6: o7k.plugin.v1.ExecuteRequest.row:type_name -> o7k.plugin.v1.Row + 13, // 7: o7k.plugin.v1.ExecuteResponse.result:type_name -> o7k.plugin.v1.Result + 14, // 8: o7k.plugin.v1.Result.details:type_name -> o7k.plugin.v1.Details + 15, // 9: o7k.plugin.v1.Result.navigate:type_name -> o7k.plugin.v1.Navigate + 17, // 10: o7k.plugin.v1.Navigate.scope:type_name -> o7k.plugin.v1.Navigate.ScopeEntry + 1, // 11: o7k.plugin.v1.Plugin.Initialize:input_type -> o7k.plugin.v1.InitializeRequest + 0, // 12: o7k.plugin.v1.Plugin.GetMetadata:input_type -> o7k.plugin.v1.Empty + 0, // 13: o7k.plugin.v1.Plugin.GetResources:input_type -> o7k.plugin.v1.Empty + 9, // 14: o7k.plugin.v1.Plugin.List:input_type -> o7k.plugin.v1.ListRequest + 11, // 15: o7k.plugin.v1.Plugin.Execute:input_type -> o7k.plugin.v1.ExecuteRequest + 0, // 16: o7k.plugin.v1.Host.GetContext:input_type -> o7k.plugin.v1.Empty + 0, // 17: o7k.plugin.v1.Plugin.Initialize:output_type -> o7k.plugin.v1.Empty + 2, // 18: o7k.plugin.v1.Plugin.GetMetadata:output_type -> o7k.plugin.v1.Metadata + 4, // 19: o7k.plugin.v1.Plugin.GetResources:output_type -> o7k.plugin.v1.Resources + 10, // 20: o7k.plugin.v1.Plugin.List:output_type -> o7k.plugin.v1.ListResponse + 12, // 21: o7k.plugin.v1.Plugin.Execute:output_type -> o7k.plugin.v1.ExecuteResponse + 3, // 22: o7k.plugin.v1.Host.GetContext:output_type -> o7k.plugin.v1.Context + 17, // [17:23] is the sub-list for method output_type + 11, // [11:17] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_pluginsdk_proto_plugin_proto_init() } +func file_pluginsdk_proto_plugin_proto_init() { + if File_pluginsdk_proto_plugin_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_proto_rawDesc), len(file_plugin_proto_rawDesc)), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_pluginsdk_proto_plugin_proto_rawDesc), len(file_pluginsdk_proto_plugin_proto_rawDesc)), NumEnums: 0, - NumMessages: 20, + NumMessages: 18, NumExtensions: 0, NumServices: 2, }, - GoTypes: file_plugin_proto_goTypes, - DependencyIndexes: file_plugin_proto_depIdxs, - MessageInfos: file_plugin_proto_msgTypes, + GoTypes: file_pluginsdk_proto_plugin_proto_goTypes, + DependencyIndexes: file_pluginsdk_proto_plugin_proto_depIdxs, + MessageInfos: file_pluginsdk_proto_plugin_proto_msgTypes, }.Build() - File_plugin_proto = out.File - file_plugin_proto_goTypes = nil - file_plugin_proto_depIdxs = nil + File_pluginsdk_proto_plugin_proto = out.File + file_pluginsdk_proto_plugin_proto_goTypes = nil + file_pluginsdk_proto_plugin_proto_depIdxs = nil } diff --git a/pluginsdk/proto/plugin.proto b/pluginsdk/proto/plugin.proto index 6027b76..2796d04 100644 --- a/pluginsdk/proto/plugin.proto +++ b/pluginsdk/proto/plugin.proto @@ -26,6 +26,7 @@ message Metadata { string name = 1; string version = 2; string color = 3; + string url = 4; } message Context { @@ -59,7 +60,6 @@ message Command { string key = 1; string description = 2; bool default = 3; - string status_label = 4; } message Row { @@ -69,7 +69,6 @@ message Row { message ListRequest { string resource = 1; - map scope = 2; } message ListResponse { @@ -80,7 +79,6 @@ message ExecuteRequest { string resource = 1; Command command = 2; Row row = 3; - map scope = 4; } message ExecuteResponse { diff --git a/pluginsdk/proto/plugin_grpc.pb.go b/pluginsdk/proto/plugin_grpc.pb.go index 57f28bb..94dfd45 100644 --- a/pluginsdk/proto/plugin_grpc.pb.go +++ b/pluginsdk/proto/plugin_grpc.pb.go @@ -2,7 +2,7 @@ // versions: // - protoc-gen-go-grpc v1.6.2 // - protoc v5.29.3 -// source: plugin.proto +// source: pluginsdk/proto/plugin.proto package proto @@ -269,7 +269,7 @@ var Plugin_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "plugin.proto", + Metadata: "pluginsdk/proto/plugin.proto", } const ( @@ -371,5 +371,5 @@ var Host_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "plugin.proto", + Metadata: "pluginsdk/proto/plugin.proto", } diff --git a/scripts/generate-proto.sh b/scripts/generate-proto.sh new file mode 100755 index 0000000..98ae010 --- /dev/null +++ b/scripts/generate-proto.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +protoc \ + --go_out=. \ + --go_opt=paths=source_relative \ + --go-grpc_out=. \ + --go-grpc_opt=paths=source_relative \ + pluginsdk/proto/plugin.proto \ No newline at end of file From 6ec9778ca0e5e6e89995abc199fd6f91c220d18b Mon Sep 17 00:00:00 2001 From: Kyriakos Akriotis Date: Mon, 5 Oct 2026 07:37:08 +0200 Subject: [PATCH 2/4] added remote url to grcp client and server, regenerated the correct proto #93 --- pluginsdk/grpc_client.go | 1 + pluginsdk/grpc_server.go | 1 + pluginsdk/proto/plugin.pb.go | 105 ++++++++++++++++++++++++----------- pluginsdk/proto/plugin.proto | 3 + 4 files changed, 77 insertions(+), 33 deletions(-) diff --git a/pluginsdk/grpc_client.go b/pluginsdk/grpc_client.go index e3e68e5..b301c8b 100644 --- a/pluginsdk/grpc_client.go +++ b/pluginsdk/grpc_client.go @@ -20,6 +20,7 @@ func (c *grpcClient) Metadata() (Metadata, error) { Name: response.Name, Version: response.Version, Color: response.Color, + URL: response.Url, }, nil } diff --git a/pluginsdk/grpc_server.go b/pluginsdk/grpc_server.go index 7015df7..b2bc3a5 100644 --- a/pluginsdk/grpc_server.go +++ b/pluginsdk/grpc_server.go @@ -35,6 +35,7 @@ func (s *grpcServer) GetMetadata(context.Context, *pb.Empty) (*pb.Metadata, erro Name: metadata.Name, Version: metadata.Version, Color: metadata.Color, + Url: metadata.URL, }, nil } diff --git a/pluginsdk/proto/plugin.pb.go b/pluginsdk/proto/plugin.pb.go index 019b413..4f6d29d 100644 --- a/pluginsdk/proto/plugin.pb.go +++ b/pluginsdk/proto/plugin.pb.go @@ -438,6 +438,7 @@ type Command struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` Default bool `protobuf:"varint,3,opt,name=default,proto3" json:"default,omitempty"` + StatusLabel string `protobuf:"bytes,4,opt,name=status_label,json=statusLabel,proto3" json:"status_label,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -493,6 +494,13 @@ func (x *Command) GetDefault() bool { return false } +func (x *Command) GetStatusLabel() string { + if x != nil { + return x.StatusLabel + } + return "" +} + type Row struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -548,6 +556,7 @@ func (x *Row) GetFields() map[string]string { type ListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + Scope map[string]string `protobuf:"bytes,2,rep,name=scope,proto3" json:"scope,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -589,6 +598,13 @@ func (x *ListRequest) GetResource() string { return "" } +func (x *ListRequest) GetScope() map[string]string { + if x != nil { + return x.Scope + } + return nil +} + type ListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Rows []*Row `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` @@ -638,6 +654,7 @@ type ExecuteRequest struct { Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` Command *Command `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` Row *Row `protobuf:"bytes,3,opt,name=row,proto3" json:"row,omitempty"` + Scope map[string]string `protobuf:"bytes,4,rep,name=scope,proto3" json:"scope,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -693,6 +710,13 @@ func (x *ExecuteRequest) GetRow() *Row { return nil } +func (x *ExecuteRequest) GetScope() map[string]string { + if x != nil { + return x.Scope + } + return nil +} + type ExecuteResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Result *Result `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` @@ -951,25 +975,36 @@ const file_pluginsdk_proto_plugin_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05title\x18\x02 \x01(\tR\x05title\x12\x1b\n" + "\tmin_width\x18\x03 \x01(\x05R\bminWidth\x12\x12\n" + - "\x04flex\x18\x04 \x01(\x05R\x04flex\"W\n" + + "\x04flex\x18\x04 \x01(\x05R\x04flex\"z\n" + "\aCommand\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x18\n" + - "\adefault\x18\x03 \x01(\bR\adefault\"\x88\x01\n" + + "\adefault\x18\x03 \x01(\bR\adefault\x12!\n" + + "\fstatus_label\x18\x04 \x01(\tR\vstatusLabel\"\x88\x01\n" + "\x03Row\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x126\n" + "\x06fields\x18\x02 \x03(\v2\x1e.o7k.plugin.v1.Row.FieldsEntryR\x06fields\x1a9\n" + "\vFieldsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\")\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa0\x01\n" + "\vListRequest\x12\x1a\n" + - "\bresource\x18\x01 \x01(\tR\bresource\"6\n" + + "\bresource\x18\x01 \x01(\tR\bresource\x12;\n" + + "\x05scope\x18\x02 \x03(\v2%.o7k.plugin.v1.ListRequest.ScopeEntryR\x05scope\x1a8\n" + + "\n" + + "ScopeEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"6\n" + "\fListResponse\x12&\n" + - "\x04rows\x18\x01 \x03(\v2\x12.o7k.plugin.v1.RowR\x04rows\"\x84\x01\n" + + "\x04rows\x18\x01 \x03(\v2\x12.o7k.plugin.v1.RowR\x04rows\"\xfe\x01\n" + "\x0eExecuteRequest\x12\x1a\n" + "\bresource\x18\x01 \x01(\tR\bresource\x120\n" + "\acommand\x18\x02 \x01(\v2\x16.o7k.plugin.v1.CommandR\acommand\x12$\n" + - "\x03row\x18\x03 \x01(\v2\x12.o7k.plugin.v1.RowR\x03row\"@\n" + + "\x03row\x18\x03 \x01(\v2\x12.o7k.plugin.v1.RowR\x03row\x12>\n" + + "\x05scope\x18\x04 \x03(\v2(.o7k.plugin.v1.ExecuteRequest.ScopeEntryR\x05scope\x1a8\n" + + "\n" + + "ScopeEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"@\n" + "\x0fExecuteResponse\x12-\n" + "\x06result\x18\x01 \x01(\v2\x15.o7k.plugin.v1.ResultR\x06result\"o\n" + "\x06Result\x120\n" + @@ -1011,7 +1046,7 @@ func file_pluginsdk_proto_plugin_proto_rawDescGZIP() []byte { return file_pluginsdk_proto_plugin_proto_rawDescData } -var file_pluginsdk_proto_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_pluginsdk_proto_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_pluginsdk_proto_plugin_proto_goTypes = []any{ (*Empty)(nil), // 0: o7k.plugin.v1.Empty (*InitializeRequest)(nil), // 1: o7k.plugin.v1.InitializeRequest @@ -1030,37 +1065,41 @@ var file_pluginsdk_proto_plugin_proto_goTypes = []any{ (*Details)(nil), // 14: o7k.plugin.v1.Details (*Navigate)(nil), // 15: o7k.plugin.v1.Navigate nil, // 16: o7k.plugin.v1.Row.FieldsEntry - nil, // 17: o7k.plugin.v1.Navigate.ScopeEntry + nil, // 17: o7k.plugin.v1.ListRequest.ScopeEntry + nil, // 18: o7k.plugin.v1.ExecuteRequest.ScopeEntry + nil, // 19: o7k.plugin.v1.Navigate.ScopeEntry } var file_pluginsdk_proto_plugin_proto_depIdxs = []int32{ 5, // 0: o7k.plugin.v1.Resources.resources:type_name -> o7k.plugin.v1.Resource 6, // 1: o7k.plugin.v1.Resource.columns:type_name -> o7k.plugin.v1.Column 7, // 2: o7k.plugin.v1.Resource.commands:type_name -> o7k.plugin.v1.Command 16, // 3: o7k.plugin.v1.Row.fields:type_name -> o7k.plugin.v1.Row.FieldsEntry - 8, // 4: o7k.plugin.v1.ListResponse.rows:type_name -> o7k.plugin.v1.Row - 7, // 5: o7k.plugin.v1.ExecuteRequest.command:type_name -> o7k.plugin.v1.Command - 8, // 6: o7k.plugin.v1.ExecuteRequest.row:type_name -> o7k.plugin.v1.Row - 13, // 7: o7k.plugin.v1.ExecuteResponse.result:type_name -> o7k.plugin.v1.Result - 14, // 8: o7k.plugin.v1.Result.details:type_name -> o7k.plugin.v1.Details - 15, // 9: o7k.plugin.v1.Result.navigate:type_name -> o7k.plugin.v1.Navigate - 17, // 10: o7k.plugin.v1.Navigate.scope:type_name -> o7k.plugin.v1.Navigate.ScopeEntry - 1, // 11: o7k.plugin.v1.Plugin.Initialize:input_type -> o7k.plugin.v1.InitializeRequest - 0, // 12: o7k.plugin.v1.Plugin.GetMetadata:input_type -> o7k.plugin.v1.Empty - 0, // 13: o7k.plugin.v1.Plugin.GetResources:input_type -> o7k.plugin.v1.Empty - 9, // 14: o7k.plugin.v1.Plugin.List:input_type -> o7k.plugin.v1.ListRequest - 11, // 15: o7k.plugin.v1.Plugin.Execute:input_type -> o7k.plugin.v1.ExecuteRequest - 0, // 16: o7k.plugin.v1.Host.GetContext:input_type -> o7k.plugin.v1.Empty - 0, // 17: o7k.plugin.v1.Plugin.Initialize:output_type -> o7k.plugin.v1.Empty - 2, // 18: o7k.plugin.v1.Plugin.GetMetadata:output_type -> o7k.plugin.v1.Metadata - 4, // 19: o7k.plugin.v1.Plugin.GetResources:output_type -> o7k.plugin.v1.Resources - 10, // 20: o7k.plugin.v1.Plugin.List:output_type -> o7k.plugin.v1.ListResponse - 12, // 21: o7k.plugin.v1.Plugin.Execute:output_type -> o7k.plugin.v1.ExecuteResponse - 3, // 22: o7k.plugin.v1.Host.GetContext:output_type -> o7k.plugin.v1.Context - 17, // [17:23] is the sub-list for method output_type - 11, // [11:17] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 17, // 4: o7k.plugin.v1.ListRequest.scope:type_name -> o7k.plugin.v1.ListRequest.ScopeEntry + 8, // 5: o7k.plugin.v1.ListResponse.rows:type_name -> o7k.plugin.v1.Row + 7, // 6: o7k.plugin.v1.ExecuteRequest.command:type_name -> o7k.plugin.v1.Command + 8, // 7: o7k.plugin.v1.ExecuteRequest.row:type_name -> o7k.plugin.v1.Row + 18, // 8: o7k.plugin.v1.ExecuteRequest.scope:type_name -> o7k.plugin.v1.ExecuteRequest.ScopeEntry + 13, // 9: o7k.plugin.v1.ExecuteResponse.result:type_name -> o7k.plugin.v1.Result + 14, // 10: o7k.plugin.v1.Result.details:type_name -> o7k.plugin.v1.Details + 15, // 11: o7k.plugin.v1.Result.navigate:type_name -> o7k.plugin.v1.Navigate + 19, // 12: o7k.plugin.v1.Navigate.scope:type_name -> o7k.plugin.v1.Navigate.ScopeEntry + 1, // 13: o7k.plugin.v1.Plugin.Initialize:input_type -> o7k.plugin.v1.InitializeRequest + 0, // 14: o7k.plugin.v1.Plugin.GetMetadata:input_type -> o7k.plugin.v1.Empty + 0, // 15: o7k.plugin.v1.Plugin.GetResources:input_type -> o7k.plugin.v1.Empty + 9, // 16: o7k.plugin.v1.Plugin.List:input_type -> o7k.plugin.v1.ListRequest + 11, // 17: o7k.plugin.v1.Plugin.Execute:input_type -> o7k.plugin.v1.ExecuteRequest + 0, // 18: o7k.plugin.v1.Host.GetContext:input_type -> o7k.plugin.v1.Empty + 0, // 19: o7k.plugin.v1.Plugin.Initialize:output_type -> o7k.plugin.v1.Empty + 2, // 20: o7k.plugin.v1.Plugin.GetMetadata:output_type -> o7k.plugin.v1.Metadata + 4, // 21: o7k.plugin.v1.Plugin.GetResources:output_type -> o7k.plugin.v1.Resources + 10, // 22: o7k.plugin.v1.Plugin.List:output_type -> o7k.plugin.v1.ListResponse + 12, // 23: o7k.plugin.v1.Plugin.Execute:output_type -> o7k.plugin.v1.ExecuteResponse + 3, // 24: o7k.plugin.v1.Host.GetContext:output_type -> o7k.plugin.v1.Context + 19, // [19:25] is the sub-list for method output_type + 13, // [13:19] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_pluginsdk_proto_plugin_proto_init() } @@ -1074,7 +1113,7 @@ func file_pluginsdk_proto_plugin_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_pluginsdk_proto_plugin_proto_rawDesc), len(file_pluginsdk_proto_plugin_proto_rawDesc)), NumEnums: 0, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 2, }, diff --git a/pluginsdk/proto/plugin.proto b/pluginsdk/proto/plugin.proto index 2796d04..889c032 100644 --- a/pluginsdk/proto/plugin.proto +++ b/pluginsdk/proto/plugin.proto @@ -60,6 +60,7 @@ message Command { string key = 1; string description = 2; bool default = 3; + string status_label = 4; } message Row { @@ -69,6 +70,7 @@ message Row { message ListRequest { string resource = 1; + map scope = 2; } message ListResponse { @@ -79,6 +81,7 @@ message ExecuteRequest { string resource = 1; Command command = 2; Row row = 3; + map scope = 4; } message ExecuteResponse { From dff6b7c755d95e2820a85710413a6841ca1eb7d7 Mon Sep 17 00:00:00 2001 From: Kyriakos Akriotis Date: Mon, 5 Oct 2026 09:26:34 +0200 Subject: [PATCH 3/4] added plugin update support for cli and app #93 --- cmd/main.go | 56 ++++++ internal/plugins/discovery.go | 4 +- internal/plugins/install.go | 116 ++++++++---- internal/plugins/manager.go | 3 + internal/plugins/paths.go | 25 +++ internal/plugins/update.go | 245 ++++++++++++++++++++++++++ internal/resources/plugins/plugins.go | 54 ++++-- internal/ui/app.go | 20 +++ 8 files changed, 467 insertions(+), 56 deletions(-) create mode 100644 internal/plugins/paths.go create mode 100644 internal/plugins/update.go diff --git a/cmd/main.go b/cmd/main.go index 5d60750..345a65d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -215,6 +215,10 @@ func pluginCommand(args []string) error { return nil } + if len(args) == 1 && args[0] == "update" { + return updatePlugins() + } + if len(args) != 2 { printPluginUsage() return fmt.Errorf("invalid plugin command") @@ -225,10 +229,12 @@ func pluginCommand(args []string) error { if err := plugins.Install(context.Background(), args[1]); err != nil { return fmt.Errorf("error installing plugin: %w", err) } + case "remove": if err := plugins.Remove(args[1]); err != nil { return fmt.Errorf("error removing plugin: %w", err) } + default: printPluginUsage() return fmt.Errorf("unknown plugin command %q", args[0]) @@ -237,21 +243,71 @@ func pluginCommand(args []string) error { return nil } +func updatePlugins() error { + openstackContext := openstack.Context{} + host := plugins.NewHost(&openstackContext) + + paths, err := plugins.Discover() + if err != nil { + return fmt.Errorf("discovering plugins: %w", err) + } + + for _, path := range paths { + client, err := plugins.NewClient(path, host) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading plugin %s: %v\n", path, err) + continue + } + + metadata, err := client.Plugin().Metadata() + client.Close() + if err != nil { + fmt.Fprintf(os.Stderr, "error getting plugin metadata %s: %v\n", path, err) + continue + } + + plugin := plugins.Info{ + Path: path, + Name: metadata.Name, + Version: metadata.Version, + URL: metadata.URL, + } + + result, err := plugins.Update(context.Background(), plugin) + if err != nil { + fmt.Fprintf(os.Stderr, "error updating plugin %q: %v\n", plugin.Name, err) + continue + } + + if !result.Updated { + fmt.Printf("plugin %q is already up to date (%s)\n", plugin.Name, result.FromVersion) + continue + } + + fmt.Printf("updated plugin %q from %s to %s\n", plugin.Name, result.FromVersion, result.ToVersion) + } + + return nil +} + func printUsage() { fmt.Fprintln(os.Stderr, "Usage:") fmt.Fprintln(os.Stderr, " o7k") fmt.Fprintln(os.Stderr, " o7k --version") fmt.Fprintln(os.Stderr, " o7k plugin install ") + fmt.Fprintln(os.Stderr, " o7k plugin update") fmt.Fprintln(os.Stderr, " o7k plugin remove ") } func printPluginUsage() { fmt.Fprintln(os.Stderr, "Usage:") fmt.Fprintln(os.Stderr, " o7k plugin install ") + fmt.Fprintln(os.Stderr, " o7k plugin update") fmt.Fprintln(os.Stderr, " o7k plugin remove ") fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Commands:") fmt.Fprintln(os.Stderr, " install Install a plugin from a local path or HTTP(S) URL") + fmt.Fprintln(os.Stderr, " update Update all installed plugins") fmt.Fprintln(os.Stderr, " remove Remove an installed plugin") } diff --git a/internal/plugins/discovery.go b/internal/plugins/discovery.go index a2c5cfc..946c433 100644 --- a/internal/plugins/discovery.go +++ b/internal/plugins/discovery.go @@ -8,13 +8,11 @@ import ( ) func Discover() ([]string, error) { - configDir, err := os.UserConfigDir() + dir, err := useUserPluginsDir() if err != nil { return nil, err } - dir := filepath.Join(configDir, "o7k", "plugins") - entries, err := os.ReadDir(dir) if err != nil { if errors.Is(err, fs.ErrNotExist) { diff --git a/internal/plugins/install.go b/internal/plugins/install.go index 90ec589..690d54d 100644 --- a/internal/plugins/install.go +++ b/internal/plugins/install.go @@ -41,67 +41,55 @@ func installLocal(source string) error { } func installRemote(ctx context.Context, source *url.URL) error { - request, err := http.NewRequestWithContext(ctx, http.MethodGet, source.String(), nil) + body, err := download(ctx, source.String()) if err != nil { - return fmt.Errorf("creating request: %w", err) - } - - response, err := http.DefaultClient.Do(request) - if err != nil { - return fmt.Errorf("downloading plugin: %w", err) - } - defer response.Body.Close() - - if response.StatusCode < 200 || response.StatusCode >= 300 { - return fmt.Errorf("downloading plugin: %s", response.Status) + return err } + defer body.Close() name := filepath.Base(source.Path) if name == "." || name == "/" || name == "" { return fmt.Errorf("plugin URL does not contain a filename") } - return install(name, response.Body) + return install(name, body) } func install(name string, source io.Reader) error { - configDir, err := os.UserConfigDir() + pluginDir, err := useUserPluginsDir() if err != nil { - return fmt.Errorf("getting user config directory: %w", err) + return err } - pluginDir := filepath.Join(configDir, "o7k", "plugins") - if err := os.MkdirAll(pluginDir, 0755); err != nil { return fmt.Errorf("creating plugin directory: %w", err) } - temp, err := os.CreateTemp(pluginDir, ".o7k-plugin-*") + tempPath, err := writePluginTemp(pluginDir, source) if err != nil { - return fmt.Errorf("creating temporary plugin: %w", err) + return err } - - tempPath := temp.Name() defer os.Remove(tempPath) - written, err := io.Copy(temp, io.LimitReader(source, maxPluginSize+1)) + binaryName := pluginBinaryName(name) + + entries, err := os.ReadDir(pluginDir) if err != nil { - temp.Close() - return fmt.Errorf("writing plugin: %w", err) + return fmt.Errorf("reading plugin directory: %w", err) } - if written > maxPluginSize { - temp.Close() - return fmt.Errorf("plugin exceeds maximum size of %d MiB", maxPluginSize>>20) - } + for _, entry := range entries { + if entry.IsDir() || entry.Name() == name { + continue + } - if err := temp.Chmod(0755); err != nil { - temp.Close() - return fmt.Errorf("making plugin executable: %w", err) - } + if pluginBinaryName(entry.Name()) != binaryName { + continue + } - if err := temp.Close(); err != nil { - return fmt.Errorf("closing plugin: %w", err) + if err := os.Remove(filepath.Join(pluginDir, entry.Name())); err != nil { + return fmt.Errorf("removing previous plugin version: %w", err) + } } destination := filepath.Join(pluginDir, name) @@ -120,12 +108,12 @@ func Remove(name string) error { return fmt.Errorf("invalid plugin name %q", name) } - configDir, err := os.UserConfigDir() + pluginDir, err := useUserPluginsDir() if err != nil { - return fmt.Errorf("getting user config directory: %w", err) + return err } - path := filepath.Join(configDir, "o7k", "plugins", name) + path := filepath.Join(pluginDir, name) if err := os.Remove(path); err != nil { return fmt.Errorf("removing plugin %q: %w", name, err) @@ -135,3 +123,57 @@ func Remove(name string) error { return nil } + +func download(ctx context.Context, source string) (io.ReadCloser, error) { + request, err := http.NewRequestWithContext(ctx, http.MethodGet, source, nil) + if err != nil { + return nil, fmt.Errorf("creating request: %w", err) + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, fmt.Errorf("downloading plugin: %w", err) + } + + if response.StatusCode < 200 || response.StatusCode >= 300 { + response.Body.Close() + return nil, fmt.Errorf("downloading plugin: %s", response.Status) + } + + return response.Body, nil +} + +func writePluginTemp(pluginDir string, source io.Reader) (string, error) { + temp, err := os.CreateTemp(pluginDir, ".o7k-plugin-*") + if err != nil { + return "", fmt.Errorf("creating temporary plugin: %w", err) + } + + tempPath := temp.Name() + + written, err := io.Copy(temp, io.LimitReader(source, maxPluginSize+1)) + if err != nil { + temp.Close() + os.Remove(tempPath) + return "", fmt.Errorf("writing plugin: %w", err) + } + + if written > maxPluginSize { + temp.Close() + os.Remove(tempPath) + return "", fmt.Errorf("plugin exceeds maximum size of %d MiB", maxPluginSize>>20) + } + + if err := temp.Chmod(0755); err != nil { + temp.Close() + os.Remove(tempPath) + return "", fmt.Errorf("making plugin executable: %w", err) + } + + if err := temp.Close(); err != nil { + os.Remove(tempPath) + return "", fmt.Errorf("closing plugin: %w", err) + } + + return tempPath, nil +} diff --git a/internal/plugins/manager.go b/internal/plugins/manager.go index 6e1cbed..6c00cf9 100644 --- a/internal/plugins/manager.go +++ b/internal/plugins/manager.go @@ -17,6 +17,7 @@ type Info struct { Path string Name string Version string + URL string Status Status Err error } @@ -67,6 +68,7 @@ func (m *Manager) Load(path string) error { Name: metadata.Name, Version: metadata.Version, Status: StatusFailed, + URL: metadata.URL, Err: err, }) @@ -79,6 +81,7 @@ func (m *Manager) Load(path string) error { Name: metadata.Name, Version: metadata.Version, Status: StatusLoaded, + URL: metadata.URL, }) return nil diff --git a/internal/plugins/paths.go b/internal/plugins/paths.go new file mode 100644 index 0000000..8ac3e2a --- /dev/null +++ b/internal/plugins/paths.go @@ -0,0 +1,25 @@ +package plugins + +import ( + "fmt" + "os" + "path/filepath" +) + +func useUserPluginsDir() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", fmt.Errorf("getting user config directory: %w", err) + } + + return filepath.Join(configDir, "o7k", "plugins"), nil +} + +func useUserPluginsBackupDir() (string, error) { + dir, err := useUserPluginsDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, "backup"), nil +} diff --git a/internal/plugins/update.go b/internal/plugins/update.go new file mode 100644 index 0000000..ba542ed --- /dev/null +++ b/internal/plugins/update.go @@ -0,0 +1,245 @@ +package plugins + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + + "golang.org/x/mod/semver" +) + +type UpdateResult struct { + Updated bool + FromVersion string + ToVersion string +} + +func Update(ctx context.Context, plugin Info) (UpdateResult, error) { + result := UpdateResult{ + FromVersion: plugin.Version, + ToVersion: plugin.Version, + } + + if plugin.Path == "" { + return result, fmt.Errorf("plugin path is empty") + } + + if plugin.URL == "" { + plugin.URL = "https://github.com/akyriako/o7k-opentelekomcloud-plugin" + //return result, fmt.Errorf("plugin %q does not provide an update URL", plugin.Name) + } + + repository, err := parseGitHubRepository(plugin.URL) + if err != nil { + return result, err + } + + release, err := getLatestGitHubRelease(ctx, repository) + if err != nil { + return result, err + } + + newer, err := hasNewerVersion(plugin.Version, release.TagName) + if err != nil { + return result, err + } + + if !newer { + return result, nil + } + + result.ToVersion = strings.TrimPrefix(release.TagName, "v") + + asset, err := findReleaseAsset(release) + if err != nil { + return result, err + } + + body, err := download(ctx, asset.BrowserDownloadURL) + if err != nil { + return result, err + } + defer body.Close() + + pluginDir, err := useUserPluginsDir() + if err != nil { + return result, err + } + + tempPath, err := writePluginTemp(pluginDir, body) + if err != nil { + return result, err + } + defer os.Remove(tempPath) + + backupDir, err := useUserPluginsBackupDir() + if err != nil { + return result, err + } + + if err := os.MkdirAll(backupDir, 0755); err != nil { + return result, fmt.Errorf("creating plugin backup directory: %w", err) + } + + binaryName := pluginBinaryName(asset.Name) + + entries, err := os.ReadDir(pluginDir) + if err != nil { + return result, fmt.Errorf("reading plugin directory: %w", err) + } + + for _, entry := range entries { + if entry.IsDir() || filepath.Join(pluginDir, entry.Name()) == plugin.Path { + continue + } + + if pluginBinaryName(entry.Name()) != binaryName { + continue + } + + if err := os.Remove(filepath.Join(pluginDir, entry.Name())); err != nil { + return result, fmt.Errorf("removing previous plugin version: %w", err) + } + } + + backupPath := filepath.Join(backupDir, filepath.Base(plugin.Path)) + destination := filepath.Join(pluginDir, asset.Name) + + if err := os.Remove(backupPath); err != nil && !os.IsNotExist(err) { + return result, fmt.Errorf("removing previous plugin backup: %w", err) + } + + if err := os.Rename(plugin.Path, backupPath); err != nil { + return result, fmt.Errorf("backing up plugin: %w", err) + } + + if err := os.Rename(tempPath, destination); err != nil { + if restoreErr := os.Rename(backupPath, plugin.Path); restoreErr != nil { + return result, fmt.Errorf("installing plugin: %w; restoring backup: %v", err, restoreErr) + } + + return result, fmt.Errorf("installing plugin: %w", err) + } + + result.Updated = true + + return result, nil +} + +type githubRepository struct { + Owner string + Name string +} + +type githubRelease struct { + TagName string `json:"tag_name"` + Assets []githubReleaseAsset `json:"assets"` +} + +type githubReleaseAsset struct { + Name string `json:"name"` + BrowserDownloadURL string `json:"browser_download_url"` +} + +func parseGitHubRepository(rawURL string) (githubRepository, error) { + u, err := url.Parse(rawURL) + if err != nil { + return githubRepository{}, fmt.Errorf("parsing plugin URL %q: %w", rawURL, err) + } + + if u.Scheme != "https" || u.Host != "github.com" { + return githubRepository{}, fmt.Errorf("unsupported plugin URL %q", rawURL) + } + + parts := strings.Split(strings.Trim(u.Path, "/"), "/") + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return githubRepository{}, fmt.Errorf("invalid GitHub repository URL %q", rawURL) + } + + return githubRepository{ + Owner: parts[0], + Name: strings.TrimSuffix(parts[1], ".git"), + }, nil +} + +func getLatestGitHubRelease(ctx context.Context, repository githubRepository) (githubRelease, error) { + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repository.Owner, repository.Name) + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return githubRelease{}, fmt.Errorf("creating GitHub release request: %w", err) + } + + request.Header.Set("Accept", "application/vnd.github+json") + request.Header.Set("User-Agent", "o7k") + + response, err := http.DefaultClient.Do(request) + if err != nil { + return githubRelease{}, fmt.Errorf("getting latest GitHub release: %w", err) + } + defer response.Body.Close() + + if response.StatusCode < 200 || response.StatusCode >= 300 { + return githubRelease{}, fmt.Errorf("getting latest GitHub release: %s", response.Status) + } + + var release githubRelease + if err := json.NewDecoder(response.Body).Decode(&release); err != nil { + return githubRelease{}, fmt.Errorf("decoding latest GitHub release: %w", err) + } + + return release, nil +} + +func normalizeVersion(version string) string { + version = strings.TrimSpace(version) + + if version != "" && !strings.HasPrefix(version, "v") { + version = "v" + version + } + + return version +} + +func hasNewerVersion(current, latest string) (bool, error) { + currentVersion := normalizeVersion(current) + latestVersion := normalizeVersion(latest) + + if !semver.IsValid(currentVersion) { + return false, fmt.Errorf("invalid installed plugin version %q", current) + } + + if !semver.IsValid(latestVersion) { + return false, fmt.Errorf("invalid release version %q", latest) + } + + return semver.Compare(latestVersion, currentVersion) > 0, nil +} + +func findReleaseAsset(release githubRelease) (githubReleaseAsset, error) { + suffix := fmt.Sprintf("_%s_%s", runtime.GOOS, runtime.GOARCH) + + for _, asset := range release.Assets { + if strings.HasSuffix(asset.Name, suffix) { + return asset, nil + } + } + + return githubReleaseAsset{}, fmt.Errorf("release %q has no asset for %s/%s", release.TagName, runtime.GOOS, runtime.GOARCH) +} + +func pluginBinaryName(name string) string { + suffix := fmt.Sprintf("_%s_%s", runtime.GOOS, runtime.GOARCH) + name = strings.TrimSuffix(name, suffix) + + if i := strings.LastIndex(name, "_"); i >= 0 { + name = name[:i] + } + + return name +} diff --git a/internal/resources/plugins/plugins.go b/internal/resources/plugins/plugins.go index c957ed8..3c67c79 100644 --- a/internal/resources/plugins/plugins.go +++ b/internal/resources/plugins/plugins.go @@ -13,6 +13,11 @@ type Resource struct { manager *pluginmanager.Manager } +type UpdatedMsg struct { + Result pluginmanager.UpdateResult + Err error +} + func New(manager *pluginmanager.Manager) *Resource { return &Resource{ manager: manager, @@ -33,16 +38,18 @@ func (r *Resource) Aliases() []string { func (r *Resource) Columns() []resource.Column { return []resource.Column{ - {Key: "name", Title: "NAME", MinWidth: 20, Flex: 1}, + {Key: "name", Title: "NAME", MinWidth: 20, Flex: 2}, {Key: "version", Title: "VERSION", MinWidth: 12}, {Key: "status", Title: "STATUS", MinWidth: 10}, - {Key: "path", Title: "PATH", MinWidth: 30, Flex: 2}, + {Key: "url", Title: "REMOTE URL", MinWidth: 30, Flex: 5}, + {Key: "path", Title: "PATH", MinWidth: 30, Flex: 5}, } } func (r *Resource) Commands() []resource.Command { return []resource.Command{ {Key: "d", Description: "Details", Default: true}, + {Key: "u", Description: "Update", StatusLabel: "Updating"}, } } @@ -67,6 +74,7 @@ func (r *Resource) List(context.Context) ([]resource.Row, error) { "name": name, "version": version, "status": string(plugin.Status), + "url": plugin.URL, "path": plugin.Path, }, }) @@ -82,25 +90,39 @@ func (r *Resource) Execute(command resource.Command, row resource.Row) tea.Cmd { continue } - content := map[string]any{ - "name": plugin.Name, - "version": plugin.Version, - "status": plugin.Status, - "path": plugin.Path, - } - - if plugin.Err != nil { - content["error"] = plugin.Err.Error() + switch command.Key { + case "d": + content := map[string]any{ + "name": plugin.Name, + "version": plugin.Version, + "status": plugin.Status, + "url": plugin.URL, + "path": plugin.Path, + } + + if plugin.Err != nil { + content["error"] = plugin.Err.Error() + } + + return resource.DetailsMsg{ + ID: row.ID, + Content: content, + } + + case "u": + result, err := pluginmanager.Update(context.Background(), plugin) + return UpdatedMsg{ + Result: result, + Err: err, + } } - return resource.DetailsMsg{ - ID: row.ID, - Content: content, + return resource.ErrorMsg{ + Err: fmt.Errorf("unsupported plugin command %q", command.Key), } } - return resource.DetailsMsg{ - ID: row.ID, + return resource.ErrorMsg{ Err: fmt.Errorf("plugin %q not found", row.ID), } } diff --git a/internal/ui/app.go b/internal/ui/app.go index fbbd98c..69caf14 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -11,6 +11,7 @@ import ( "github.com/akyriako/o7k/internal/plugins" "github.com/akyriako/o7k/internal/resource" "github.com/akyriako/o7k/internal/resources/contexts" + pluginresource "github.com/akyriako/o7k/internal/resources/plugins" "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" @@ -566,6 +567,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.loadingLabel = "" return m, nil + case pluginresource.UpdatedMsg: + if msg.Err != nil { + m.err = fmt.Errorf("updating plugin failed: %v", msg.Err) + slog.Error(m.err.Error(), "cloud", m.context.Cloud) + return m, nil + } + + if !msg.Result.Updated { + m.status = fmt.Sprintf("plugin is already up to date (%s)", msg.Result.FromVersion) + return m, clearStatus(m.status) + } + + m.status = fmt.Sprintf( + "plugin updated from %s to %s; restart o7k to use the new version", + msg.Result.FromVersion, + msg.Result.ToVersion, + ) + + return m, clearStatus(m.status) } var cmd tea.Cmd From 014ca15d9875304c6e4704197b500629ee2a8084 Mon Sep 17 00:00:00 2001 From: Kyriakos Akriotis Date: Mon, 5 Oct 2026 09:28:38 +0200 Subject: [PATCH 4/4] removed the temporary shortwiring for plugin.URL for debug and testing #93 --- internal/plugins/update.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/plugins/update.go b/internal/plugins/update.go index ba542ed..757d7bc 100644 --- a/internal/plugins/update.go +++ b/internal/plugins/update.go @@ -31,8 +31,7 @@ func Update(ctx context.Context, plugin Info) (UpdateResult, error) { } if plugin.URL == "" { - plugin.URL = "https://github.com/akyriako/o7k-opentelekomcloud-plugin" - //return result, fmt.Errorf("plugin %q does not provide an update URL", plugin.Name) + return result, fmt.Errorf("plugin %q does not provide an update URL", plugin.Name) } repository, err := parseGitHubRepository(plugin.URL)