package plugin import ( "context" "errors" "fmt" "io" "time" "connectrpc.com/connect" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" commonv1 "code.icb4dc0.de/buildr/api/generated/common/v1" wasiv1 "code.icb4dc0.de/buildr/api/generated/wasi/v1" "code.icb4dc0.de/buildr/api/generated/wasi/v1/rpcv1connect" "code.icb4dc0.de/buildr/common/wasirpc" "code.icb4dc0.de/buildr/buildr/modules" ) func (m Module) Help(ctx context.Context) (help modules.Help, err error) { runtime := m.prepareWASIRuntime(ctx) defer func() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() _ = runtime.Close(ctx) }() _, err = runtime.NewHostModuleBuilder("buildr"). NewFunctionBuilder().WithFunc(dummyWithResult).Export("log_msg"). NewFunctionBuilder().WithFunc(dummyWithResult).Export("get_state"). NewFunctionBuilder().WithFunc(dummyWithResult).Export("set_state"). NewFunctionBuilder().WithFunc(dummyWithResult).Export("exec"). NewFunctionBuilder().WithFunc(dummyWithResult).Export("lookPath"). Instantiate(ctx) if err != nil { return modules.Help{}, err } closer, err := wasi_snapshot_preview1.Instantiate(ctx, runtime) if err != nil { return modules.Help{}, err } defer func() { if closeErr := closer.Close(context.Background()); closeErr != nil { err = errors.Join(err, fmt.Errorf("failed to close WASI runtime: %w", closeErr)) } }() moduleConfig := wazero.NewModuleConfig(). WithStdout(io.Discard). WithStderr(io.Discard) pluginPayload, err := m.PluginPayload.Bytes() if err != nil { return modules.Help{}, err } mod, err := runtime.InstantiateWithConfig(ctx, pluginPayload, moduleConfig) if err != nil { return modules.Help{}, err } moduleClient, err := wasirpc.NewModuleClient(mod) if err != nil { return modules.Help{}, fmt.Errorf("failed to create module client: %w", err) } wasiClient := rpcv1connect.NewWasiExecutorServiceClient(moduleClient, fmt.Sprintf("wasi://%s", m.PluginType)) helpRequest := &wasiv1.HelpRequest{ ModuleReference: &commonv1.ModuleReference{ ModuleCategory: m.PluginCategory, ModuleType: m.PluginType, }, } resp, err := wasiClient.Help(ctx, connect.NewRequest(helpRequest)) if err != nil { return modules.Help{}, err } moduleHelp := modules.Help{ Name: resp.Msg.Name, Description: resp.Msg.Description, Examples: m.mapToExamples(resp.Msg.Examples), } return moduleHelp, nil } func (m Module) mapToExamples(raw []*wasiv1.TaskExample) []modules.Example { examples := make([]modules.Example, 0, len(raw)) for _, example := range raw { spec := &modules.Metadata[Module]{ Module: Module{ PluginCategory: m.Category(), PluginType: m.Type(), PluginPayload: m.PluginPayload, modSpec: example.TaskSpec.ModuleSpec, }, ModuleName: example.TaskSpec.ModuleName, OutputDir: example.TaskSpec.OutputDir, } if example.TaskSpec.Container != nil { spec.Container = &modules.ContainerSpec{ Image: example.TaskSpec.Container.Image, User: example.TaskSpec.Container.User, BindMounts: nil, Privileged: example.TaskSpec.Container.Privileged, } if example.TaskSpec.Container.Capabilities != nil { spec.Container.Capabilities = &modules.ContainerCapabilities{ Add: example.TaskSpec.Container.Capabilities.Add, Drop: example.TaskSpec.Container.Capabilities.Drop, } } if example.TaskSpec.Container.VolumeMounts != nil { spec.Container.VolumeMounts = make([]modules.ContainerVolumeMount, 0, len(example.TaskSpec.Container.VolumeMounts)) for _, v := range example.TaskSpec.Container.VolumeMounts { spec.Container.VolumeMounts = append(spec.Container.VolumeMounts, modules.ContainerVolumeMount{ Target: v.Target, Name: v.Name, ReadOnly: v.ReadOnly, NoCopy: v.NoCopy, }) } } if example.TaskSpec.Container.TmpfsMounts != nil { spec.Container.TmpfsMounts = make([]modules.ContainerTmpfsMount, 0, len(example.TaskSpec.Container.TmpfsMounts)) for _, v := range example.TaskSpec.Container.TmpfsMounts { spec.Container.TmpfsMounts = append(spec.Container.TmpfsMounts, modules.ContainerTmpfsMount{ Target: v.Target, ReadOnly: v.ReadOnly, Size: v.Size, }) } } if example.TaskSpec.Container.BindMounts != nil { spec.Container.BindMounts = make([]modules.ContainerBindMount, 0, len(example.TaskSpec.Container.BindMounts)) for _, v := range example.TaskSpec.Container.BindMounts { spec.Container.BindMounts = append(spec.Container.BindMounts, modules.ContainerBindMount{ Source: v.Source, Target: v.Target, }) } } } examples = append(examples, modules.Example{ Name: example.Name, Description: example.Description, Spec: spec, }) } return examples }