diff --git a/cmd/flamenco-manager-poc/main.go b/cmd/flamenco-manager-poc/main.go index 0fbf7384..d8a1fe82 100644 --- a/cmd/flamenco-manager-poc/main.go +++ b/cmd/flamenco-manager-poc/main.go @@ -49,7 +49,6 @@ func main() { log.Info().Str("version", appinfo.ApplicationVersion).Msgf("starting %v", appinfo.ApplicationName) - gojaPoC() echoOpenAPIPoC() } @@ -113,7 +112,12 @@ func echoOpenAPIPoC() { }) e.Use(validator) - flamenco := api_impl.NewFlamenco() + compiler, err := job_compilers.Load() + if err != nil { + log.Fatal().Err(err).Msg("error loading job compilers") + } + + flamenco := api_impl.NewFlamenco(compiler) api.RegisterHandlers(e, flamenco) // Log available routes diff --git a/internal/manager/api_impl/api_impl.go b/internal/manager/api_impl/api_impl.go index 254d0b9c..f31cc559 100644 --- a/internal/manager/api_impl/api_impl.go +++ b/internal/manager/api_impl/api_impl.go @@ -27,12 +27,20 @@ import ( ) type Flamenco struct { + jobCompiler JobCompiler +} + +type JobCompiler interface { + ListJobTypes() api.AvailableJobTypes } var _ api.ServerInterface = (*Flamenco)(nil) -func NewFlamenco() *Flamenco { - return &Flamenco{} +// NewFlamenco creates a new Flamenco service, using the given JobCompiler. +func NewFlamenco(jc JobCompiler) *Flamenco { + return &Flamenco{ + jobCompiler: jc, + } } // sendPetstoreError wraps sending of an error in the Error format, and diff --git a/internal/manager/api_impl/job_compiler.go b/internal/manager/api_impl/job_compiler.go index 2c3f4d7f..caaf87e5 100644 --- a/internal/manager/api_impl/job_compiler.go +++ b/internal/manager/api_impl/job_compiler.go @@ -24,52 +24,15 @@ import ( "net/http" "github.com/labstack/echo/v4" - "gitlab.com/blender/flamenco-goja-test/pkg/api" + "github.com/rs/zerolog/log" ) func (f *Flamenco) GetJobTypes(e echo.Context) error { - // Some helper functions because Go doesn't allow taking the address of a literal. - defaultString := func(s string) *interface{} { - var iValue interface{} = s - return &iValue - } - defaultInt32 := func(i int32) *interface{} { - var iValue interface{} = i - return &iValue - } - defaultBool := func(b bool) *interface{} { - var iValue interface{} = b - return &iValue - } - boolPtr := func(b bool) *bool { - return &b - } - choicesStr := func(choices ...string) *[]string { - return &choices + if f.jobCompiler == nil { + log.Error().Msg("Flamenco is running without job compiler") + return sendAPIError(e, http.StatusInternalServerError, "no job types available") } - // TODO: dynamically build based on the actually registered job types. - types := api.AvailableJobTypes{ - JobTypes: []api.AvailableJobType{{ - Name: "simple-blender-render", - Settings: []api.AvailableJobSetting{ - {Key: "blender_cmd", Type: "string", Default: defaultString("{blender}")}, - {Key: "chunk_size", Type: "int32", Default: defaultInt32(1)}, - {Key: "frames", Type: "string", Required: boolPtr(true)}, - {Key: "render_output", Type: "string", Required: boolPtr(true)}, - {Key: "fps", Type: "int32"}, - {Key: "extract_audio", Type: "bool", Default: defaultBool(true)}, - {Key: "images_or_video", - Type: "string", - Required: boolPtr(true), - Choices: choicesStr("images", "video"), - Visible: boolPtr(false), - }, - {Key: "format", Type: "string", Required: boolPtr(true)}, - {Key: "output_file_extension", Type: "string", Required: boolPtr(true)}, - }, - }}, - } - - return e.JSON(http.StatusOK, &types) + jobTypes := f.jobCompiler.ListJobTypes() + return e.JSON(http.StatusOK, &jobTypes) } diff --git a/internal/manager/job_compilers/job_compilers.go b/internal/manager/job_compilers/job_compilers.go index 0e3600ac..6fd3bf7f 100644 --- a/internal/manager/job_compilers/job_compilers.go +++ b/internal/manager/job_compilers/job_compilers.go @@ -1,3 +1,5 @@ +// Package job_compilers contains functionality to convert a Flamenco job +// definition into concrete tasks and commands to execute by Workers. package job_compilers /* ***** BEGIN GPL LICENSE BLOCK ***** @@ -28,24 +30,32 @@ import ( "github.com/dop251/goja_nodejs/require" "github.com/google/uuid" "github.com/rs/zerolog/log" + "gitlab.com/blender/flamenco-goja-test/pkg/api" ) var ErrJobTypeUnknown = errors.New("job type unknown") var ErrScriptIncomplete = errors.New("job compiler script incomplete") -type GojaJobCompiler struct { - jobtypes map[string]JobType // Mapping from job type name to jobType struct. - registry *require.Registry // Goja module registry. +// Service contains job compilers defined in JavaScript. +type Service struct { + compilers map[string]Compiler // Mapping from job type name to the job compiler of that type. + registry *require.Registry // Goja module registry. } -type JobType struct { +type Compiler struct { + jobType string program *goja.Program // Compiled JavaScript file. filename string // The filename of that JS file. } -func Load() (*GojaJobCompiler, error) { - compiler := GojaJobCompiler{ - jobtypes: map[string]JobType{}, +type VM struct { + runtime *goja.Runtime // Goja VM containing the job compiler script. + compiler Compiler // Program loaded into this VM. +} + +func Load() (*Service, error) { + compiler := Service{ + compilers: map[string]Compiler{}, } if err := compiler.loadScripts(); err != nil { @@ -53,7 +63,7 @@ func Load() (*GojaJobCompiler, error) { } staticFileLoader := func(path string) ([]byte, error) { - content, err := compiler.loadScript(path) + content, err := compiler.loadScriptBytes(path) if err != nil { // The 'require' module uses this to try different variations of the path // in order to find it (without .js, with .js, etc.), so don't log any of @@ -71,34 +81,15 @@ func Load() (*GojaJobCompiler, error) { return &compiler, nil } -func (c *GojaJobCompiler) newGojaVM() *goja.Runtime { - vm := goja.New() - vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) - - mustSet := func(name string, value interface{}) { - err := vm.Set(name, value) - if err != nil { - log.Panic().Err(err).Msgf("unable to register '%s' in Goja VM", name) - } +func (s *Service) Run(jobTypeName string) error { + vm, err := s.compilerForJobType(jobTypeName) + if err != nil { + return err } - // Set some global functions for script debugging purposes. - mustSet("print", jsPrint) - mustSet("alert", jsAlert) - - // Pre-import some useful modules. - c.registry.Enable(vm) - mustSet("author", require.Require(vm, "author")) - mustSet("path", require.Require(vm, "path")) - mustSet("process", require.Require(vm, "process")) - - return vm -} - -func (c *GojaJobCompiler) Run(jobTypeName string) error { - jobType, ok := c.jobtypes[jobTypeName] - if !ok { - return ErrJobTypeUnknown + compileJob, err := vm.getCompileJob() + if err != nil { + return err } created, err := time.Parse(time.RFC3339, "2022-01-03T18:53:00+01:00") @@ -130,24 +121,7 @@ func (c *GojaJobCompiler) Run(jobTypeName string) error { }, } - vm := c.newGojaVM() - - // This should register the `compileJob()` function called below: - if _, err := vm.RunProgram(jobType.program); err != nil { - return err - } - - compileJob, isCallable := goja.AssertFunction(vm.Get("compileJob")) - if !isCallable { - log.Error(). - Str("jobType", jobTypeName). - Str("script", jobType.filename). - Msg("script does not define a compileJob(job) function") - return ErrScriptIncomplete - - } - - if _, err := compileJob(nil, vm.ToValue(&job)); err != nil { + if err := compileJob(&job); err != nil { return err } @@ -159,3 +133,60 @@ func (c *GojaJobCompiler) Run(jobTypeName string) error { return nil } + +// ListJobTypes returns the list of available job types. +func (s *Service) ListJobTypes() api.AvailableJobTypes { + jobTypes := make([]api.AvailableJobType, 0) + for typeName := range s.compilers { + compiler, err := s.compilerForJobType(typeName) + if err != nil { + log.Warn().Err(err).Str("jobType", typeName).Msg("unable to determine job type settings") + continue + } + + description, err := compiler.getJobTypeInfo() + if err != nil { + log.Warn().Err(err).Str("jobType", typeName).Msg("unable to determine job type settings") + continue + } + + jobTypes = append(jobTypes, *description) + } + return api.AvailableJobTypes{JobTypes: jobTypes} +} + +func (vm *VM) getCompileJob() (func(job *AuthoredJob) error, error) { + compileJob, isCallable := goja.AssertFunction(vm.runtime.Get("compileJob")) + if !isCallable { + // TODO: construct a more elaborate Error object that contains this info, instead of logging here. + log.Error(). + Str("jobType", vm.compiler.jobType). + Str("script", vm.compiler.filename). + Msg("script does not define a compileJob(job) function") + return nil, ErrScriptIncomplete + } + + // TODO: wrap this in a nicer way. + return func(job *AuthoredJob) error { + _, err := compileJob(nil, vm.runtime.ToValue(job)) + return err + }, nil +} + +func (vm *VM) getJobTypeInfo() (*api.AvailableJobType, error) { + jtValue := vm.runtime.Get("JOB_TYPE") + + var ajt api.AvailableJobType + if err := vm.runtime.ExportTo(jtValue, &ajt); err != nil { + // TODO: construct a more elaborate Error object that contains this info, instead of logging here. + log.Error(). + Err(err). + Str("jobType", vm.compiler.jobType). + Str("script", vm.compiler.filename). + Msg("script does not define a proper JOB_TYPE object") + return nil, ErrScriptIncomplete + } + + ajt.Name = vm.compiler.jobType + return &ajt, nil +} diff --git a/internal/manager/job_compilers/scripts.go b/internal/manager/job_compilers/scripts.go index 3a880fcd..b16bc374 100644 --- a/internal/manager/job_compilers/scripts.go +++ b/internal/manager/job_compilers/scripts.go @@ -28,13 +28,14 @@ import ( "strings" "github.com/dop251/goja" + "github.com/dop251/goja_nodejs/require" "github.com/rs/zerolog/log" ) //go:embed scripts var scriptsFS embed.FS -func (c *GojaJobCompiler) loadScripts() error { +func (s *Service) loadScripts() error { scripts, err := scriptsFS.ReadDir("scripts") if err != nil { return fmt.Errorf("failed to find scripts: %w", err) @@ -46,7 +47,7 @@ func (c *GojaJobCompiler) loadScripts() error { } filename := path.Join("scripts", script.Name()) - script_bytes, err := c.loadScript(filename) + script_bytes, err := s.loadScriptBytes(filename) if err != nil { log.Error().Err(err).Str("filename", filename).Msg("failed to read script") continue @@ -59,7 +60,7 @@ func (c *GojaJobCompiler) loadScripts() error { } jobTypeName := filenameToJobType(script.Name()) - c.jobtypes[jobTypeName] = JobType{ + s.compilers[jobTypeName] = Compiler{ program: program, filename: script.Name(), } @@ -70,7 +71,7 @@ func (c *GojaJobCompiler) loadScripts() error { return nil } -func (c *GojaJobCompiler) loadScript(path string) ([]byte, error) { +func (s *Service) loadScriptBytes(path string) ([]byte, error) { file, err := scriptsFS.Open(path) if err != nil { return nil, fmt.Errorf("failed to open embedded script: %w", err) @@ -83,3 +84,46 @@ func filenameToJobType(filename string) string { stem := filename[:len(filename)-len(extension)] return strings.ReplaceAll(stem, "_", "-") } + +func (s *Service) newGojaVM() *goja.Runtime { + vm := goja.New() + vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) + + mustSet := func(name string, value interface{}) { + err := vm.Set(name, value) + if err != nil { + log.Panic().Err(err).Msgf("unable to register '%s' in Goja VM", name) + } + } + + // Set some global functions for script debugging purposes. + mustSet("print", jsPrint) + mustSet("alert", jsAlert) + + // Pre-import some useful modules. + s.registry.Enable(vm) + mustSet("author", require.Require(vm, "author")) + mustSet("path", require.Require(vm, "path")) + mustSet("process", require.Require(vm, "process")) + + return vm +} + +// compilerForJobType returns a Goja *Runtime that has the job compiler script for +// the given job type loaded up. +func (s *Service) compilerForJobType(jobTypeName string) (*VM, error) { + program, ok := s.compilers[jobTypeName] + if !ok { + return nil, ErrJobTypeUnknown + } + + vm := s.newGojaVM() + if _, err := vm.RunProgram(program.program); err != nil { + return nil, err + } + + return &VM{ + runtime: vm, + compiler: program, + }, nil +} diff --git a/internal/manager/job_compilers/scripts/simple_blender_render.js b/internal/manager/job_compilers/scripts/simple_blender_render.js index da3cfed8..269ee20f 100644 --- a/internal/manager/job_compilers/scripts/simple_blender_render.js +++ b/internal/manager/job_compilers/scripts/simple_blender_render.js @@ -18,6 +18,28 @@ * * ***** END GPL LICENSE BLOCK ***** */ +const JOB_TYPE = { + label: "Simple Blender Render", + settings: [ + { key: "blender_cmd", type: "string", default: "{blender}" }, + { key: "chunk_size", type: "int32", default: 1 }, + { key: "frames", type: "string", required: true }, + { key: "render_output", type: "string", required: true }, + { key: "fps", type: "int32" }, + { key: "extract_audio", type: "bool", default: true }, + { + key: "images_or_video", + type: "string", + required: true, + choices: ["images", "video"], + visible: false, + }, + { key: "format", type: "string", required: true }, + { key: "output_file_extension", type: "string", required: true }, + ] +}; + + // Set of scene.render.image_settings.file_format values that produce // files which FFmpeg is known not to handle as input. const ffmpegIncompatibleImageFormats = new Set([ diff --git a/pkg/api/flamenco-manager.yaml b/pkg/api/flamenco-manager.yaml index 32db81a3..10df1917 100644 --- a/pkg/api/flamenco-manager.yaml +++ b/pkg/api/flamenco-manager.yaml @@ -77,6 +77,8 @@ paths: tags: - name: worker description: API for Flamenco Workers to communicate with Flamenco Manager. + - name: jobs + description: Blablabla for users to get job metadata balbla components: schemas: @@ -154,10 +156,11 @@ components: description: Job type supported by this Manager, and its parameters. properties: "name": {type: string} + "label": {type: string} "settings": type: array items: {$ref: "#/components/schemas/AvailableJobSetting"} - required: [name, settings] + required: [name, label, settings] AvailableJobSetting: type: object diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go index 19c51425..c875ca3c 100644 --- a/pkg/api/openapi_spec.gen.go +++ b/pkg/api/openapi_spec.gen.go @@ -18,35 +18,36 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "MSpkhCWapcH+wNhV97f7fwcAAP//KSYvdP0WAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/api/openapi_types.gen.go b/pkg/api/openapi_types.gen.go index 234dc8bf..d6ebced8 100644 --- a/pkg/api/openapi_types.gen.go +++ b/pkg/api/openapi_types.gen.go @@ -81,6 +81,7 @@ type AvailableJobSettingType string // Job type supported by this Manager, and its parameters. type AvailableJobType struct { + Label string `json:"label"` Name string `json:"name"` Settings []AvailableJobSetting `json:"settings"` }