From 9158939c98ed2dc024eabae25056835f074d5a10 Mon Sep 17 00:00:00 2001 From: Brett Goulder Date: Mon, 7 Feb 2022 20:24:33 -0800 Subject: [PATCH] meroxa apps run initial spike --- cmd/meroxa/root/apps/apps.go | 55 +++++++++++ cmd/meroxa/root/apps/common.go | 26 +++++ cmd/meroxa/root/apps/run.go | 162 +++++++++++++++++++++++++++++++ cmd/meroxa/root/apps/run_test.go | 45 +++++++++ cmd/meroxa/root/root.go | 2 + 5 files changed, 290 insertions(+) create mode 100644 cmd/meroxa/root/apps/apps.go create mode 100644 cmd/meroxa/root/apps/common.go create mode 100644 cmd/meroxa/root/apps/run.go create mode 100644 cmd/meroxa/root/apps/run_test.go diff --git a/cmd/meroxa/root/apps/apps.go b/cmd/meroxa/root/apps/apps.go new file mode 100644 index 000000000..eae4ba4d3 --- /dev/null +++ b/cmd/meroxa/root/apps/apps.go @@ -0,0 +1,55 @@ +/* +Copyright © 2022 Meroxa Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apps + +import ( + "github.com/meroxa/cli/cmd/meroxa/builder" + "github.com/spf13/cobra" +) + +type Apps struct{} + +var ( + _ builder.CommandWithDocs = (*Apps)(nil) + _ builder.CommandWithAliases = (*Apps)(nil) + _ builder.CommandWithSubCommands = (*Apps)(nil) + _ builder.CommandWithHidden = (*Apps)(nil) +) + +func (*Apps) Aliases() []string { + return []string{"app"} +} + +func (*Apps) Usage() string { + return "apps" +} + +func (*Apps) Hidden() bool { + return true +} + +func (*Apps) Docs() builder.Docs { + return builder.Docs{ + Short: "Manage Meroxa Data Applications", + } +} + +func (*Apps) SubCommands() []*cobra.Command { + return []*cobra.Command{ + builder.BuildCobraCommand(&Run{}), + } +} diff --git a/cmd/meroxa/root/apps/common.go b/cmd/meroxa/root/apps/common.go new file mode 100644 index 000000000..90ae91bb1 --- /dev/null +++ b/cmd/meroxa/root/apps/common.go @@ -0,0 +1,26 @@ +package apps + +import ( + "context" + "os/exec" + + "github.com/meroxa/cli/log" +) + +func buildGoApp(ctx context.Context, l log.Logger, appPath, appName string, platform bool) error { + var cmd *exec.Cmd + if platform { + cmd = exec.Command("go", "build", "--tags", "platform", "-o", "./"+appPath, "./"+appPath+"/...") //nolint:gosec + } else { + cmd = exec.Command("go", "build", "-o", appName, "./...") + } + cmd.Dir = appPath + + stdout, err := cmd.CombinedOutput() + if err != nil { + l.Error(ctx, string(stdout)) + return err + } + + return nil +} diff --git a/cmd/meroxa/root/apps/run.go b/cmd/meroxa/root/apps/run.go new file mode 100644 index 000000000..ea8838540 --- /dev/null +++ b/cmd/meroxa/root/apps/run.go @@ -0,0 +1,162 @@ +/* +Copyright © 2022 Meroxa Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apps + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path" + + "github.com/meroxa/cli/cmd/meroxa/builder" + "github.com/meroxa/cli/log" +) + +type Run struct { + flags struct { + // `--lang` is not required unless language is not specified via app.json + Lang string `long:"lang" short:"l" usage:"language to use (go | js)"` + Path string `long:"path" usage:"path of application to run"` + } + + logger log.Logger +} + +var ( + _ builder.CommandWithDocs = (*Run)(nil) + _ builder.CommandWithFlags = (*Run)(nil) + _ builder.CommandWithExecute = (*Run)(nil) + _ builder.CommandWithLogger = (*Run)(nil) +) + +type AppConfig struct { + Language string +} + +func (*Run) Usage() string { + return "run" +} + +func (*Run) Docs() builder.Docs { + return builder.Docs{ + Short: "Execute a Meroxa Data Application locally", + Long: "meroxa apps run will build your app locally to then run it\n" + + "locally on --path.", + Example: "meroxa apps run # assumes you run it from the app directory\n" + + "meroxa apps run --path ../js-demo --lang js # in case you didn't specify lang on your app.json" + + "meroxa apps run --path ../go-demo # it'll use lang defined in your app.json", + } +} + +func (r *Run) Flags() []builder.Flag { + return builder.BuildFlags(&r.flags) +} + +func (r *Run) buildGoApp(ctx context.Context, appPath string) error { + // grab current location to use it as project name + appName := path.Base(appPath) + + // building is a requirement prior to running for go apps + err := buildGoApp(ctx, r.logger, appPath, appName, false) + if err != nil { + return err + } + + err = os.Chdir(appPath) + if err != nil { + return err + } + + cmd := exec.Command("./" + appName) //nolint:gosec + + out, err := cmd.CombinedOutput() + if err != nil { + r.logger.Error(ctx, err.Error()) + } + + r.logger.Info(ctx, string(out)) + return nil +} + +func (r *Run) buildJSApp(ctx context.Context) error { + // TODO: Handle the requirement of https://github.com/meroxa/turbine-js.git being installed + // cd into the path first + cmd := exec.Command("npx", "turbine", "test") + stdout, err := cmd.CombinedOutput() + if err != nil { + return err + } + r.logger.Info(ctx, string(stdout)) + return nil +} + +func (r *Run) readConfigFile() (AppConfig, error) { + var appConfig AppConfig + + appPath := r.flags.Path + appConfigPath := path.Join(appPath, "app.json") + appConfigBytes, err := os.ReadFile(appConfigPath) + if err != nil { + return appConfig, fmt.Errorf("%v\n"+ + "Applications to run require an app.json file", err) + } + if err := json.Unmarshal(appConfigBytes, &appConfig); err != nil { + return appConfig, err + } + + return appConfig, nil +} + +func (r *Run) Execute(ctx context.Context) error { + var appPath string + + switch { + case r.flags.Path != "": + appPath = r.flags.Path + default: + appPath = "." + } + + appConfig, err := r.readConfigFile() + if err != nil { + return err + } + + lang := appConfig.Language + + if lang == "" { + if r.flags.Lang == "" { + return fmt.Errorf("flag --lang is required unless lang is specified in your app.json") + } + lang = r.flags.Lang + } + + switch lang { + case "go", "golang": + return r.buildGoApp(ctx, appPath) + case "js", "javascript", "nodejs": + return r.buildJSApp(ctx) + default: + return fmt.Errorf("language %q not supported. Currently, we support \"javascript\" and \"go\"", lang) + } +} + +func (r *Run) Logger(logger log.Logger) { + r.logger = logger +} diff --git a/cmd/meroxa/root/apps/run_test.go b/cmd/meroxa/root/apps/run_test.go new file mode 100644 index 000000000..db13b9d89 --- /dev/null +++ b/cmd/meroxa/root/apps/run_test.go @@ -0,0 +1,45 @@ +package apps + +import ( + "testing" + + "github.com/meroxa/cli/cmd/meroxa/builder" + "github.com/meroxa/cli/utils" +) + +func TestRunAppFlags(t *testing.T) { + expectedFlags := []struct { + name string + required bool + shorthand string + hidden bool + }{ + {name: "lang", shorthand: "l"}, + {name: "path", required: false}, + } + + c := builder.BuildCobraCommand(&Run{}) + + for _, f := range expectedFlags { + cf := c.Flags().Lookup(f.name) + if cf == nil { + t.Fatalf("expected flag \"%s\" to be present", f.name) + } + + if f.shorthand != cf.Shorthand { + t.Fatalf("expected shorthand \"%s\" got \"%s\" for flag \"%s\"", f.shorthand, cf.Shorthand, f.name) + } + + if f.required && !utils.IsFlagRequired(cf) { + t.Fatalf("expected flag \"%s\" to be required", f.name) + } + + if cf.Hidden != f.hidden { + if cf.Hidden { + t.Fatalf("expected flag \"%s\" not to be hidden", f.name) + } else { + t.Fatalf("expected flag \"%s\" to be hidden", f.name) + } + } + } +} diff --git a/cmd/meroxa/root/root.go b/cmd/meroxa/root/root.go index 884b350b2..d859cd966 100644 --- a/cmd/meroxa/root/root.go +++ b/cmd/meroxa/root/root.go @@ -24,6 +24,7 @@ import ( "github.com/meroxa/cli/cmd/meroxa/global" "github.com/meroxa/cli/cmd/meroxa/root/api" + "github.com/meroxa/cli/cmd/meroxa/root/apps" "github.com/meroxa/cli/cmd/meroxa/root/auth" "github.com/meroxa/cli/cmd/meroxa/root/billing" "github.com/meroxa/cli/cmd/meroxa/root/config" @@ -80,6 +81,7 @@ meroxa resources list --types cmd.AddCommand(builder.BuildCobraCommand(&api.API{})) cmd.AddCommand(builder.BuildCobraCommand(&auth.Auth{})) + cmd.AddCommand(builder.BuildCobraCommand(&apps.Apps{})) cmd.AddCommand(builder.BuildCobraCommand(&billing.Billing{})) cmd.AddCommand(builder.BuildCobraCommand(&config.Config{})) cmd.AddCommand(builder.BuildCobraCommand(&connectors.Connect{}))