Skip to content

Conversation

eternal-flame-AD
Copy link
Member

very much WIP, expect big changes as I go.

Just sharing to get some early opinions.

TODO:

  • Finalize the architecture
  • A shim that takes a V1 plugin and returns a V2 plugin
  • A test server and test client
  • Scout existing issues to add missing features

Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
Signed-off-by: eternal-flame-AD <[email protected]>
@eternal-flame-AD
Copy link
Member Author

@jmattheis Can you take a look when you have time before I do the server side integration? I think basic functionality and consistency tests should be all there.

Comment on lines +3 to +6
on:
push:
branches:
- main
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This project doesn't have a main branch, only a master branch. Could you change this to push & pull_request, so that possible contributions are tested too?

flagSet.StringVar(&kexReqFileName, "kex-req-file", "", "File name for the key exchange for Transport Auth. /proc/self/fd/* can be used to open a file descriptor cross platform.")
flagSet.StringVar(&kexRespFileName, "kex-resp-file", "", "File name for the key exchange for Transport Auth. /proc/self/fd/* can be used to open a file descriptor cross platform.")
flagSet.BoolVar(&debug, "debug", false, "Enable debug mode.")
flagSet.Parse(args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns an error, please check/return it.

Comment on lines +56 to +58
if err != nil {
return nil, err
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already checked?

Comment on lines +46 to +48
if err != nil {
return nil, err
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this one line up, after the creation of err? Is also done above for keyReqFile

if err == io.EOF {
break
}
panic(err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use t.Fatal() instead, there are multiple panic calls in the tests.


tlsWebhookName := transport.BuildPluginTLSName(transport.PurposePluginWebhook, pluginInfo.ModulePath)

for range 3 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is difficult to read and IMO a little weird. The webhook is called 8x3 times, but I guess the later ones aren't counted because of the graceful shut down that is also called 8 times. Could we do this without the for loops and explicitly recv the messages. E.g.

	// for each capability
	msg, err := stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Capable{}, msg.Update)

	msg, err := stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Capable{}, msg.Update)

	msg, err = stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Capable{}, msg.Update)

	displayerClient := protobuf.NewDisplayerClient(rpcClient)
	displayResponse, err := displayerClient.Display(context.Background(), &protobuf.DisplayRequest{
		User: &protobuf.UserContext{
			Id:    uint64(testUser.ID),
			Name:  testUser.Name,
			Admin: testUser.Admin,
		},
		Location: "https://gotify.example.com/",
	})
	require.NoError(t, err)
	require.Equal(t, displayResponse.GetMarkdown(), "...")

	tlsWebhookName := transport.BuildPluginTLSName(transport.PurposePluginWebhook, pluginInfo.ModulePath)

	// first request
	testreq := httptest.NewRequest("GET", "https://"+tlsWebhookName+"/plugin/echo-test/echo", nil)
	recorder := httptest.NewRecorder()
	compatV1.ServeHTTP(recorder, testreq)
	// ...

	msg, err = stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Storage{}, msg.Update)
	msg, err = stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Message{}, msg.Update)

	// second request
	testreq := httptest.NewRequest("GET", "https://"+tlsWebhookName+"/plugin/echo-test/echo", nil)
	recorder := httptest.NewRecorder()
	compatV1.ServeHTTP(recorder, testreq)
	// ...

	msg, err = stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Storage{}, msg.Update)
	msg, err = stream.Recv()
	require.NoError(t, err)
	require.Equal(t, protobuf.InstanceUpdate_Message{}, msg.Update)

Copy link
Member Author

@eternal-flame-AD eternal-flame-AD Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is more or less a sanity check of "plugins built on older plugin architecture/mental model will continue to work" by moving the current tested plugins here.

This package is still largely only supportive code, schemas and prototypes for implementing a plugin, so I think a behavioral test is okay.

@@ -0,0 +1,35 @@
package main
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR only supports running the plugins via the shim? Are you planning to support a new way with a func main implementation, so that we don't have to use plugin.Open?

Copy link
Member Author

@eternal-flame-AD eternal-flame-AD Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, currently the only end-to-end ones are through shims. The gRPC protocols I think already defined what you need to support to be accepted as a plugin (and largely it's behavior, I know we need a formal documentation but I think we can do it just before finalizing everything.

I am trying to not build too much excess abstraction on top of gRPC to enable people to write plugins in any language easily. I would personally be happy with going with the duck typing model by just saying "implement this gRPC server and key exchange behavior and it is a plugin: a Python script will work too":

at the end the documentation will say something like "Gotify will call your program with these arguments, you are supposed to respond like this ... and if it does that it is a plugin-regardless of how it is implemented. We have the plugin-api package with pre-built schemas but you are welcome to use protoc files in another language"

Comment on lines +336 to +341
s.shim.mu.RLock()
instance, ok := s.shim.instances[req.User.Id]
s.shim.mu.RUnlock()
if !ok {
return nil, errors.New("instance not found")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be extracted into a method e.g. instance, err := s.getInstance(req.user.id)

}

func (h *shimV1StorageHandler) Save(b []byte) error {
h.currentStorage = slices.Clone(b)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get race-conditions here? I'm not sure if we allow running Save/Load in different threads/goroutines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants