diff --git a/Makefile b/Makefile index e7567f2f7..6b54bf153 100644 --- a/Makefile +++ b/Makefile @@ -34,3 +34,7 @@ endif .PHONY: lint lint: docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:latest golangci-lint run --timeout 5m -v + +.PHONY: mock +mock: + mockgen -source cmd/meroxa/turbine_cli/interface.go -package mock > cmd/meroxa/turbine_cli/mock/cli.go diff --git a/cmd/meroxa/root/apps/apps.go b/cmd/meroxa/root/apps/apps.go index a572ef29c..b50818797 100644 --- a/cmd/meroxa/root/apps/apps.go +++ b/cmd/meroxa/root/apps/apps.go @@ -63,5 +63,6 @@ func (*Apps) SubCommands() []*cobra.Command { builder.BuildCobraCommand(&Logs{}), builder.BuildCobraCommand(&Remove{}), builder.BuildCobraCommand(&Run{}), + builder.BuildCobraCommand(&Upgrade{}), } } diff --git a/cmd/meroxa/root/apps/deploy.go b/cmd/meroxa/root/apps/deploy.go index 22b0ee24d..343d3a141 100644 --- a/cmd/meroxa/root/apps/deploy.go +++ b/cmd/meroxa/root/apps/deploy.go @@ -32,10 +32,10 @@ import ( "github.com/coreos/go-semver/semver" "github.com/meroxa/cli/cmd/meroxa/builder" "github.com/meroxa/cli/cmd/meroxa/global" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" - turbineGo "github.com/meroxa/cli/cmd/meroxa/turbine_cli/golang" - turbineJS "github.com/meroxa/cli/cmd/meroxa/turbine_cli/javascript" - turbinePY "github.com/meroxa/cli/cmd/meroxa/turbine_cli/python" + turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine" + turbineGo "github.com/meroxa/cli/cmd/meroxa/turbine/golang" + turbineJS "github.com/meroxa/cli/cmd/meroxa/turbine/javascript" + turbinePY "github.com/meroxa/cli/cmd/meroxa/turbine/python" "github.com/meroxa/cli/config" "github.com/meroxa/cli/log" "github.com/meroxa/meroxa-go/pkg/meroxa" @@ -609,17 +609,7 @@ func (d *Deploy) prepareDeployment(ctx context.Context) error { func (d *Deploy) rmBinary() { if d.lang == GoLang { - localBinary := filepath.Join(d.path, d.appName) - err := os.Remove(localBinary) - if err != nil { - fmt.Printf("warning: failed to clean up %s\n", localBinary) - } - - crossCompiledBinary := filepath.Join(d.path, d.appName) + ".cross" - err = os.Remove(crossCompiledBinary) - if err != nil { - fmt.Printf("warning: failed to clean up %s\n", crossCompiledBinary) - } + turbineGo.RunCleanup(d.path, d.appName) } } diff --git a/cmd/meroxa/root/apps/deploy_test.go b/cmd/meroxa/root/apps/deploy_test.go index 02d3411bd..48402bde7 100644 --- a/cmd/meroxa/root/apps/deploy_test.go +++ b/cmd/meroxa/root/apps/deploy_test.go @@ -9,7 +9,7 @@ import ( "github.com/golang/mock/gomock" "github.com/meroxa/cli/cmd/meroxa/builder" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/config" "github.com/meroxa/cli/log" "github.com/meroxa/cli/utils" @@ -368,13 +368,13 @@ func TestTearDownExistingResourcesWithAppNotFound(t *testing.T) { func TestGetResourceCheckErrorMessage(t *testing.T) { testCases := []struct { name string - resources []turbineCLI.ApplicationResource + resources []turbine.ApplicationResource resourceState string expectedErrorMessage string }{ { name: "getResourceCheckErrorMessage returns an empty response if all resources are found and available", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "nozzle", }, @@ -387,7 +387,7 @@ func TestGetResourceCheckErrorMessage(t *testing.T) { }, { name: "getResourceCheckErrorMessage returns an error response if resources are unavailable", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "nozzle", }, @@ -438,12 +438,12 @@ func TestGetResourceCheckErrorMessage(t *testing.T) { func TestValidateCollections(t *testing.T) { testCases := []struct { name string - resources []turbineCLI.ApplicationResource + resources []turbine.ApplicationResource err string }{ { name: "Different source and destination resources reference different collections", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -458,7 +458,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Different source and destination resources reference same collection", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -473,7 +473,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Multiple destination resources", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -493,7 +493,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Same source and destination resources reference same collection", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "pg", Source: true, @@ -509,7 +509,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "One resource is both source and destination", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -521,7 +521,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Destination resource used in another app", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -538,7 +538,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Two same destination resources", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", Source: true, @@ -559,7 +559,7 @@ func TestValidateCollections(t *testing.T) { }, { name: "Ignore resources without collection info", - resources: []turbineCLI.ApplicationResource{ + resources: []turbine.ApplicationResource{ { Name: "source", }, diff --git a/cmd/meroxa/root/apps/init.go b/cmd/meroxa/root/apps/init.go index eb7b6285c..477363ba2 100644 --- a/cmd/meroxa/root/apps/init.go +++ b/cmd/meroxa/root/apps/init.go @@ -9,11 +9,12 @@ import ( "strings" "github.com/meroxa/cli/cmd/meroxa/builder" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" - turbinejs "github.com/meroxa/cli/cmd/meroxa/turbine_cli/javascript" - turbinepy "github.com/meroxa/cli/cmd/meroxa/turbine_cli/python" + turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine" + utils "github.com/meroxa/cli/cmd/meroxa/turbine/golang" + turbinejs "github.com/meroxa/cli/cmd/meroxa/turbine/javascript" + turbinepy "github.com/meroxa/cli/cmd/meroxa/turbine/python" "github.com/meroxa/cli/log" - turbine "github.com/meroxa/turbine-go/init" + turbinego "github.com/meroxa/turbine-go/init" ) type Init struct { @@ -129,13 +130,13 @@ func (i *Init) Execute(ctx context.Context) error { i.logger.StartSpinner("\t", fmt.Sprintf("Initializing application %q in %q...", name, i.path)) switch lang { case "go", GoLang: - err = turbine.Init(name, i.path) + err = turbinego.Init(name, i.path) if err != nil { i.logger.StopSpinnerWithStatus("\t", log.Failed) return err } i.logger.StopSpinnerWithStatus("Application directory created!", log.Successful) - err = turbineCLI.GoInit(ctx, i.logger, i.path+"/"+name, i.flags.SkipModInit, i.flags.ModVendor) + err = utils.GoInit(i.logger, i.path+"/"+name, i.flags.SkipModInit, i.flags.ModVendor) case "js", JavaScript, NodeJs: err = turbinejs.Init(ctx, i.logger, name, i.path) case "py", Python3, Python: diff --git a/cmd/meroxa/root/apps/run.go b/cmd/meroxa/root/apps/run.go index b7a4354b5..973d77e4f 100644 --- a/cmd/meroxa/root/apps/run.go +++ b/cmd/meroxa/root/apps/run.go @@ -21,17 +21,15 @@ import ( "fmt" "github.com/meroxa/cli/cmd/meroxa/builder" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" - turbineGo "github.com/meroxa/cli/cmd/meroxa/turbine_cli/golang" - turbineJS "github.com/meroxa/cli/cmd/meroxa/turbine_cli/javascript" - turbinepy "github.com/meroxa/cli/cmd/meroxa/turbine_cli/python" + turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine" + turbineGo "github.com/meroxa/cli/cmd/meroxa/turbine/golang" + turbineJS "github.com/meroxa/cli/cmd/meroxa/turbine/javascript" + turbinepy "github.com/meroxa/cli/cmd/meroxa/turbine/python" "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"` } @@ -75,14 +73,20 @@ func (r *Run) Execute(ctx context.Context) error { if err != nil { return err } - lang, err := turbineCLI.GetLang(ctx, r.logger, r.flags.Lang, r.path) + lang, err := turbineCLI.GetLangFromAppJSON(ctx, r.logger, r.path) + if err != nil { + return err + } + appName, err := turbineCLI.GetAppNameFromAppJSON(ctx, r.logger, r.path) if err != nil { return err } switch lang { case GoLang: - return turbineGo.Run(ctx, r.path, r.logger) + err = turbineGo.Run(ctx, r.path, r.logger) + turbineGo.RunCleanup(r.path, appName) + return err case "js", JavaScript, NodeJs: return turbineJS.Build(ctx, r.logger, r.path) case "py", Python3, Python: diff --git a/cmd/meroxa/root/apps/run_test.go b/cmd/meroxa/root/apps/run_test.go index db13b9d89..29fef0a7b 100644 --- a/cmd/meroxa/root/apps/run_test.go +++ b/cmd/meroxa/root/apps/run_test.go @@ -14,7 +14,6 @@ func TestRunAppFlags(t *testing.T) { shorthand string hidden bool }{ - {name: "lang", shorthand: "l"}, {name: "path", required: false}, } diff --git a/cmd/meroxa/root/apps/upgrade.go b/cmd/meroxa/root/apps/upgrade.go new file mode 100644 index 000000000..a48b5f738 --- /dev/null +++ b/cmd/meroxa/root/apps/upgrade.go @@ -0,0 +1,132 @@ +/* +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" + "fmt" + "strconv" + + "github.com/meroxa/cli/cmd/meroxa/builder" + "github.com/meroxa/cli/cmd/meroxa/turbine" + turbineGo "github.com/meroxa/cli/cmd/meroxa/turbine/golang" + turbineJS "github.com/meroxa/cli/cmd/meroxa/turbine/javascript" + turbinePY "github.com/meroxa/cli/cmd/meroxa/turbine/python" + "github.com/meroxa/cli/log" +) + +type Upgrade struct { + logger log.Logger + turbineCLI turbine.CLI + run builder.CommandWithExecute + path string + + flags struct { + Path string `long:"path" usage:"path where application exists (current directory as default)"` + } +} + +var ( + _ builder.CommandWithDocs = (*Upgrade)(nil) + _ builder.CommandWithFlags = (*Upgrade)(nil) + _ builder.CommandWithExecute = (*Upgrade)(nil) + _ builder.CommandWithLogger = (*Upgrade)(nil) +) + +func (*Upgrade) Usage() string { + return "upgrade [APP_NAME] [--path pwd]" +} + +func (*Upgrade) Docs() builder.Docs { + return builder.Docs{ + Short: "Upgrade a Turbine Data Application", + Example: `meroxa apps upgrade my-app --path ~/code`, + Beta: true, + } +} + +func (u *Upgrade) Logger(logger log.Logger) { + u.logger = logger +} + +func (u *Upgrade) Flags() []builder.Flag { + return builder.BuildFlags(&u.flags) +} + +func (u *Upgrade) Execute(ctx context.Context) error { + var err error + u.path, err = turbine.GetPath(u.flags.Path) + if err != nil { + return err + } + + u.logger.StartSpinner("\t", fmt.Sprintf(" Fetching details of application in %q...", u.path)) + config, err := turbine.ReadConfigFile(u.path) + if err != nil { + u.logger.StopSpinnerWithStatus("\t", log.Failed) + return err + } + u.logger.StopSpinnerWithStatus(fmt.Sprintf("Determined the details of the %q Application", config.Name), log.Successful) + + lang := config.Language + vendor, _ := strconv.ParseBool(config.Vendor) + switch lang { + case "go", GoLang: + if u.turbineCLI == nil { + u.turbineCLI = turbineGo.New(u.logger) + } + err = u.turbineCLI.Upgrade(u.path, vendor) + case "js", JavaScript, NodeJs: + if u.turbineCLI == nil { + u.turbineCLI = turbineJS.New(u.logger) + } + err = u.turbineCLI.Upgrade(u.path, vendor) + case "py", Python3, Python: + if u.turbineCLI == nil { + u.turbineCLI = turbinePY.New(u.logger) + } + err = u.turbineCLI.Upgrade(u.path, vendor) + default: + return fmt.Errorf("language %q not supported. %s", lang, LanguageNotSupportedError) + } + if err != nil { + return err + } + + u.logger.StartSpinner("\t", " Testing upgrades locally...") + if u.run == nil { + u.run = &Run{ + logger: log.NewWithDevNull(), + flags: struct { + Path string `long:"path" usage:"path of application to run"` + }{ + Path: u.path, + }, + } + } + err = u.run.Execute(ctx) + if err != nil { + u.logger.StopSpinnerWithStatus("Upgrades were not entirely successful."+ + " Fix any issues before adding and committing all upgrades.", log.Failed) + return err + } + u.logger.StopSpinnerWithStatus("Tested upgrades locally successfully!", log.Successful) + + u.logger.Infof(ctx, "Your Turbine Application %s has been upgraded successfully!"+ + " To finish, add and commit the upgrades.", config.Name) + return nil +} diff --git a/cmd/meroxa/root/apps/upgrade_test.go b/cmd/meroxa/root/apps/upgrade_test.go new file mode 100644 index 000000000..aeae230fc --- /dev/null +++ b/cmd/meroxa/root/apps/upgrade_test.go @@ -0,0 +1,165 @@ +package apps + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/golang/mock/gomock" + + "github.com/meroxa/cli/cmd/meroxa/builder" + "github.com/meroxa/cli/cmd/meroxa/root/nop" + turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine" + mockturbinecli "github.com/meroxa/cli/cmd/meroxa/turbine/mock" + "github.com/meroxa/cli/log" + "github.com/meroxa/cli/utils" +) + +func TestUpgradeAppFlags(t *testing.T) { + expectedFlags := []struct { + name string + required bool + shorthand string + hidden bool + }{ + {name: "path", required: false}, + } + + c := builder.BuildCobraCommand(&Upgrade{}) + + 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) + } + } + } +} + +func TestUpgradeExecute(t *testing.T) { + tests := []struct { + desc string + path string + lang string + vendor bool + err error + }{ + { + desc: "Golang upgrade without vendor", + path: "/tmp", + lang: GoLang, + vendor: false, + err: nil, + }, + { + desc: "Golang upgrade with path and vendor", + path: "/tmp", + lang: GoLang, + vendor: true, + err: nil, + }, + { + desc: "Golang upgrade with path and vendor and error", + path: "/tmp", + lang: GoLang, + vendor: true, + err: fmt.Errorf("not good"), + }, + { + desc: "Javascript with path", + path: "/tmp", + lang: JavaScript, + err: nil, + }, + { + desc: "Python with path", + path: "/tmp", + lang: Python, + err: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + _ = os.Setenv("UNIT_TEST", "true") + defer func() { + _ = os.Unsetenv("UNIT_TEST") + }() + mockCtrl := gomock.NewController(t) + + u := &Upgrade{} + u.Logger(log.NewTestLogger()) + path, err := turbinecli.GetPath(tt.path) + processError(t, err, tt.err) + u.flags.Path = path + u.run = &nop.Nop{} + + mock := mockturbinecli.NewMockCLI(mockCtrl) + if tt.err == nil { + mock.EXPECT().Upgrade(path, tt.vendor) + } else { + mock.EXPECT().Upgrade(path, tt.vendor).Return(tt.err) + } + u.turbineCLI = mock + + switch tt.lang { + case GoLang: + config := turbinecli.AppConfig{Language: GoLang} + config.Vendor = "false" + if tt.vendor { + config.Vendor = "true" + } + err = turbinecli.WriteConfigFile(path, config) + processError(t, err, tt.err) + defer func() { + _ = os.Remove(filepath.Join(path, "app.json")) + }() + case JavaScript: + _ = turbinecli.WriteConfigFile(path, turbinecli.AppConfig{Language: JavaScript}) + defer func() { + _ = os.Remove(filepath.Join(path, "app.json")) + }() + case Python: + _ = turbinecli.WriteConfigFile(path, turbinecli.AppConfig{Language: Python}) + defer func() { + _ = os.Remove(filepath.Join(path, "app.json")) + }() + default: + t.Fatalf("unprocessable language: %s", tt.lang) + } + + err = u.Execute(context.Background()) + processError(t, err, tt.err) + if err == nil && tt.err != nil { + t.Fatalf("did not find expected error: %s", tt.err.Error()) + } + }) + } +} + +func processError(t *testing.T, given error, wanted error) { + if given != nil { + if wanted == nil { + t.Fatalf("unexpected error \"%s\"", given) + } else if wanted.Error() != given.Error() { + t.Fatalf("expected \"%s\" got \"%s\"", wanted, given) + } + } +} diff --git a/cmd/meroxa/root/nop/nop.go b/cmd/meroxa/root/nop/nop.go new file mode 100644 index 000000000..4c1051237 --- /dev/null +++ b/cmd/meroxa/root/nop/nop.go @@ -0,0 +1,43 @@ +/* +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 nop + +import ( + "context" + + "github.com/meroxa/cli/cmd/meroxa/builder" +) + +type Nop struct { +} + +var ( + _ builder.CommandWithExecute = (*Nop)(nil) + _ builder.CommandWithHidden = (*Nop)(nil) +) + +func (*Nop) Hidden() bool { + return true +} + +func (*Nop) Usage() string { + return "" +} + +func (*Nop) Execute(ctx context.Context) error { + return nil +} diff --git a/cmd/meroxa/turbine_cli/golang/build.go b/cmd/meroxa/turbine/golang/build.go similarity index 100% rename from cmd/meroxa/turbine_cli/golang/build.go rename to cmd/meroxa/turbine/golang/build.go diff --git a/cmd/meroxa/turbine/golang/cli.go b/cmd/meroxa/turbine/golang/cli.go new file mode 100644 index 000000000..4ef88299f --- /dev/null +++ b/cmd/meroxa/turbine/golang/cli.go @@ -0,0 +1,14 @@ +package turbinego + +import ( + "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +type turbineGoCLI struct { + logger log.Logger +} + +func New(l log.Logger) turbine.CLI { + return &turbineGoCLI{logger: l} +} diff --git a/cmd/meroxa/turbine_cli/golang/deploy.go b/cmd/meroxa/turbine/golang/deploy.go similarity index 91% rename from cmd/meroxa/turbine_cli/golang/deploy.go rename to cmd/meroxa/turbine/golang/deploy.go index 2e5225e71..8e02b182f 100644 --- a/cmd/meroxa/turbine_cli/golang/deploy.go +++ b/cmd/meroxa/turbine/golang/deploy.go @@ -11,7 +11,7 @@ import ( "regexp" "github.com/meroxa/cli/cmd/meroxa/global" - turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) @@ -48,8 +48,8 @@ func RunDeployApp(ctx context.Context, l log.Logger, appPath, imageName, appName return nil } -func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbinecli.ApplicationResource, error) { - var resources []turbinecli.ApplicationResource +func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbine.ApplicationResource, error) { + var resources []turbine.ApplicationResource cmd := exec.Command(path.Join(appPath, appName), "--listresources") //nolint:gosec output, err := cmd.CombinedOutput() @@ -59,7 +59,7 @@ func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([ if err := json.Unmarshal(output, &resources); err != nil { // fall back if not json - return turbinecli.GetResourceNamesFromString(string(output)), nil + return turbine.GetResourceNamesFromString(string(output)), nil } return resources, nil diff --git a/cmd/meroxa/turbine_cli/golang/run.go b/cmd/meroxa/turbine/golang/run.go similarity index 62% rename from cmd/meroxa/turbine_cli/golang/run.go rename to cmd/meroxa/turbine/golang/run.go index 203963ae5..7c79a6f0e 100644 --- a/cmd/meroxa/turbine_cli/golang/run.go +++ b/cmd/meroxa/turbine/golang/run.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "github.com/meroxa/cli/log" ) @@ -35,3 +36,18 @@ func Run(ctx context.Context, appPath string, l log.Logger) error { l.Info(ctx, string(output)) return nil } + +// RunCleanup removes any dangling binaries. +func RunCleanup(path, appName string) { + localBinary := filepath.Join(path, appName) + err := os.Remove(localBinary) + if err != nil { + fmt.Printf("warning: failed to clean up %s\n", localBinary) + } + + crossCompiledBinary := localBinary + ".cross" + err = os.Remove(crossCompiledBinary) + if err != nil { + fmt.Printf("warning: failed to clean up %s\n", crossCompiledBinary) + } +} diff --git a/cmd/meroxa/turbine/golang/upgrade.go b/cmd/meroxa/turbine/golang/upgrade.go new file mode 100644 index 000000000..d0dd1261f --- /dev/null +++ b/cmd/meroxa/turbine/golang/upgrade.go @@ -0,0 +1,54 @@ +package turbinego + +import ( + "fmt" + "os" + "os/exec" + + utils "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +// Upgrade fetches the latest Meroxa dependencies. +func (t *turbineGoCLI) Upgrade(appPath string, vendor bool) error { + pwd, err := utils.SwitchToAppDirectory(appPath) + if err != nil { + return err + } + + err = GoGetDeps(t.logger) + if err != nil { + return err + } + + err = t.tidy(vendor) + if err != nil { + return err + } + + return os.Chdir(pwd) +} + +func (t *turbineGoCLI) tidy(vendor bool) error { + var err error + t.logger.StartSpinner("\t", " Tidying up Golang modules...") + if vendor { + _, err = os.Stat("vendor") + if !os.IsNotExist(err) { + cmd := exec.Command("go", "mod", "vendor") + output, err := cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to download modules to vendor", log.Failed) + return fmt.Errorf(string(output)) + } + } + cmd := exec.Command("go", "mod", "tidy") + output, err := cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to tidy modules", log.Failed) + return fmt.Errorf(string(output)) + } + } + t.logger.StopSpinnerWithStatus("Finished tidying up Golang modules successfully!", log.Successful) + return nil +} diff --git a/cmd/meroxa/turbine/golang/utils.go b/cmd/meroxa/turbine/golang/utils.go new file mode 100644 index 000000000..4ab62fea8 --- /dev/null +++ b/cmd/meroxa/turbine/golang/utils.go @@ -0,0 +1,117 @@ +package turbinego + +import ( + "fmt" + "go/build" + "os" + "os/exec" + "strings" + + utils "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +func GoInit(l log.Logger, appPath string, skipInit, vendor bool) error { + l.StartSpinner("\t", " Running golang module initializing...") + skipLog := "skipping go module initialization\n\tFor guidance, visit " + + "https://docs.meroxa.com/beta-overview#go-mod-init-for-a-new-golang-turbine-data-application" + goPath := os.Getenv("GOPATH") + if goPath == "" { + goPath = build.Default.GOPATH + } + if goPath == "" { + l.StopSpinnerWithStatus("$GOPATH not set up; "+skipLog, log.Warning) + return nil + } + i := strings.Index(appPath, goPath+"/src") + if i == -1 || i != 0 { + l.StopSpinnerWithStatus(fmt.Sprintf("%s is not under $GOPATH/src; %s", appPath, skipLog), log.Warning) + return nil + } + + // temporarily switching to the app's directory + pwd, err := utils.SwitchToAppDirectory(appPath) + if err != nil { + l.StopSpinnerWithStatus("\t", log.Failed) + return err + } + + // initialize the user's module + err = utils.SetModuleInitInAppJSON(appPath, skipInit) + if err != nil { + l.StopSpinnerWithStatus("\t", log.Failed) + return err + } + + err = modulesInit(l, appPath, skipInit, vendor) + if err != nil { + l.StopSpinnerWithStatus("\t", log.Failed) + return err + } + + return os.Chdir(pwd) +} + +func modulesInit(l log.Logger, appPath string, skipInit, vendor bool) error { + if skipInit { + return nil + } + + cmd := exec.Command("go", "mod", "init") + output, err := cmd.CombinedOutput() + if err != nil { + l.StopSpinnerWithStatus(fmt.Sprintf("%s", string(output)), log.Failed) + return err + } + successLog := "go mod init succeeded" + goPath := os.Getenv("GOPATH") + if goPath == "" { + successLog += fmt.Sprintf(" (while assuming GOPATH is %s)", build.Default.GOPATH) + } + l.StopSpinnerWithStatus(successLog+"!", log.Successful) + + err = GoGetDeps(l) + if err != nil { + return err + } + + // download dependencies + err = utils.SetVendorInAppJSON(appPath, vendor) + if err != nil { + return err + } + depsLog := "Downloading dependencies" + cmd = exec.Command("go", "mod", "download") + if vendor { + depsLog += " to vendor" + cmd = exec.Command("go", "mod", "vendor") + } + depsLog += "..." + l.StartSpinner("\t", depsLog) + output, err = cmd.CombinedOutput() + if err != nil { + l.StopSpinnerWithStatus(fmt.Sprintf("%s", string(output)), log.Failed) + return err + } + l.StopSpinnerWithStatus("Downloaded all other dependencies successfully!", log.Successful) + return nil +} + +// GoGetDeps updates the latest Meroxa mods. +func GoGetDeps(l log.Logger) error { + l.StartSpinner("\t", " Getting latest turbine-go and turbine-go/running dependencies...") + cmd := exec.Command("go", "get", "-u", "github.com/meroxa/turbine-go") + output, err := cmd.CombinedOutput() + if err != nil { + l.StopSpinnerWithStatus(fmt.Sprintf("%s", string(output)), log.Failed) + return err + } + cmd = exec.Command("go", "get", "-u", "github.com/meroxa/turbine-go/runner") + output, err = cmd.CombinedOutput() + if err != nil { + l.StopSpinnerWithStatus(fmt.Sprintf("%s", string(output)), log.Failed) + return err + } + l.StopSpinnerWithStatus("Downloaded latest turbine-go and turbine-go/running dependencies successfully!", log.Successful) + return nil +} diff --git a/cmd/meroxa/turbine/interface.go b/cmd/meroxa/turbine/interface.go new file mode 100644 index 000000000..50fa3c186 --- /dev/null +++ b/cmd/meroxa/turbine/interface.go @@ -0,0 +1,5 @@ +package turbine + +type CLI interface { + Upgrade(appPath string, vendor bool) error +} diff --git a/cmd/meroxa/turbine_cli/javascript/build.go b/cmd/meroxa/turbine/javascript/build.go similarity index 66% rename from cmd/meroxa/turbine_cli/javascript/build.go rename to cmd/meroxa/turbine/javascript/build.go index a87ff7444..001a1ac14 100644 --- a/cmd/meroxa/turbine_cli/javascript/build.go +++ b/cmd/meroxa/turbine/javascript/build.go @@ -3,8 +3,7 @@ package turbinejs import ( "context" - turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine_cli" - + utils "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) @@ -12,8 +11,8 @@ import ( func Build(ctx context.Context, l log.Logger, path string) error { // TODO: Handle the requirement of https://github.com/meroxa/turbine-js.git being installed // cd into the path first - cmd := turbinecli.RunTurbineJS("test", path) - stdOut, err := turbinecli.RunCmdWithErrorDetection(ctx, cmd, l) + cmd := RunTurbineJS("test", path) + stdOut, err := utils.RunCmdWithErrorDetection(ctx, cmd, l) l.Info(ctx, stdOut) return err } diff --git a/cmd/meroxa/turbine/javascript/cli.go b/cmd/meroxa/turbine/javascript/cli.go new file mode 100644 index 000000000..beab0185d --- /dev/null +++ b/cmd/meroxa/turbine/javascript/cli.go @@ -0,0 +1,14 @@ +package turbinejs + +import ( + "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +type turbineJsCLI struct { + logger log.Logger +} + +func New(l log.Logger) turbine.CLI { + return &turbineJsCLI{logger: l} +} diff --git a/cmd/meroxa/turbine_cli/javascript/deploy.go b/cmd/meroxa/turbine/javascript/deploy.go similarity index 78% rename from cmd/meroxa/turbine_cli/javascript/deploy.go rename to cmd/meroxa/turbine/javascript/deploy.go index 9e46bbe22..1b68d2977 100644 --- a/cmd/meroxa/turbine_cli/javascript/deploy.go +++ b/cmd/meroxa/turbine/javascript/deploy.go @@ -10,12 +10,12 @@ import ( "strconv" "github.com/meroxa/cli/cmd/meroxa/global" - turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) func NeedsToBuild(path string) (bool, error) { - cmd := turbinecli.RunTurbineJS("hasfunctions", path) + cmd := RunTurbineJS("hasfunctions", path) output, err := cmd.CombinedOutput() if err != nil { err := fmt.Errorf( @@ -38,7 +38,7 @@ func NeedsToBuild(path string) (bool, error) { } func CreateDockerfile(ctx context.Context, l log.Logger, path string) error { - cmd := turbinecli.RunTurbineJS("clibuild", path) + cmd := RunTurbineJS("clibuild", path) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("unable to create Dockerfile at %s; %s", path, string(output)) @@ -54,7 +54,7 @@ func RunDeployApp(ctx context.Context, l log.Logger, path, imageName, appName, g params = append(params, specVersion) } - cmd := turbinecli.RunTurbineJS(params...) + cmd := RunTurbineJS(params...) accessToken, _, err := global.GetUserToken() if err != nil { @@ -63,15 +63,15 @@ func RunDeployApp(ctx context.Context, l log.Logger, path, imageName, appName, g cmd.Env = os.Environ() cmd.Env = append(cmd.Env, fmt.Sprintf("MEROXA_ACCESS_TOKEN=%s", accessToken)) - _, err = turbinecli.RunCmdWithErrorDetection(ctx, cmd, l) + _, err = turbine.RunCmdWithErrorDetection(ctx, cmd, l) return err } // GetResources asks turbine for a list of resources used by the given app. -func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbinecli.ApplicationResource, error) { - var resources []turbinecli.ApplicationResource +func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbine.ApplicationResource, error) { + var resources []turbine.ApplicationResource - cmd := turbinecli.RunTurbineJS("listresources", appPath) + cmd := RunTurbineJS("listresources", appPath) output, err := cmd.CombinedOutput() if err != nil { return resources, errors.New(string(output)) @@ -79,7 +79,7 @@ func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([ if err := json.Unmarshal(output, &resources); err != nil { // fall back if not json - return turbinecli.GetResourceNamesFromString(string(output)), nil + return turbine.GetResourceNamesFromString(string(output)), nil } return resources, nil diff --git a/cmd/meroxa/turbine/javascript/init.go b/cmd/meroxa/turbine/javascript/init.go new file mode 100644 index 000000000..1126bb4fa --- /dev/null +++ b/cmd/meroxa/turbine/javascript/init.go @@ -0,0 +1,14 @@ +package turbinejs + +import ( + "context" + + utils "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +func Init(ctx context.Context, l log.Logger, name, path string) error { + cmd := RunTurbineJS("generate", name, path) + _, err := utils.RunCmdWithErrorDetection(ctx, cmd, l) + return err +} diff --git a/cmd/meroxa/turbine/javascript/upgrade.go b/cmd/meroxa/turbine/javascript/upgrade.go new file mode 100644 index 000000000..ca0b303f0 --- /dev/null +++ b/cmd/meroxa/turbine/javascript/upgrade.go @@ -0,0 +1,52 @@ +package turbinejs + +import ( + "fmt" + "os/exec" + + "github.com/meroxa/cli/log" +) + +// Upgrade fetches the latest Meroxa dependencies. +func (t *turbineJsCLI) Upgrade(appPath string, vendor bool) error { + cmd := exec.Command("grep", "turbine-js-framework", "package.json") + cmd.Dir = appPath + err := cmd.Wait() + if err != nil { + t.logger.StartSpinner("\t", " Adding @meroxa/turbine-js-framework requirement...") + cmd = exec.Command("npm", "install", "@meroxa/turbine-js-framework", "--save") + cmd.Dir = appPath + out, err := cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to install @meroxa/turbine-js-framework", log.Failed) + return fmt.Errorf(string(out)) + } + + cmd = exec.Command("npm", "uninstall", "@meroxa/turbine-js", "--save") + cmd.Dir = appPath + err = cmd.Run() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to uninstall @meroxa/turbine-js. Moving on...", log.Failed) + } + + cmd = exec.Command("npm", "update") + cmd.Dir = appPath + out, err = cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to run npm update", log.Failed) + return fmt.Errorf(string(out)) + } + t.logger.StopSpinnerWithStatus("Added @meroxa/turbine-js-framework requirement successfully!", log.Successful) + } else { + t.logger.StartSpinner("\t", " Upgrading @meroxa/turbine-js-framework...") + cmd = exec.Command("npm", "upgrade", "@meroxa/turbine-js-framework") + cmd.Dir = appPath + out, err := cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("Failed to upgrade @meroxa/turbine-js-framework", log.Failed) + return fmt.Errorf(string(out)) + } + t.logger.StopSpinnerWithStatus("Upgraded @meroxa/turbine-js-framework successfully!", log.Successful) + } + return nil +} diff --git a/cmd/meroxa/turbine/javascript/utils.go b/cmd/meroxa/turbine/javascript/utils.go new file mode 100644 index 000000000..9dcd9694a --- /dev/null +++ b/cmd/meroxa/turbine/javascript/utils.go @@ -0,0 +1,32 @@ +package turbinejs + +import ( + "fmt" + "os/exec" + + "github.com/meroxa/cli/cmd/meroxa/global" +) + +const turbineJSVersion = "1.0.0" + +var isTrue = "true" + +func RunTurbineJS(params ...string) (cmd *exec.Cmd) { + args := getTurbineJSBinary(params) + return executeTurbineJSCommand(args) +} + +func getTurbineJSBinary(params []string) []string { + shouldUseLocalTurbineJS := global.GetLocalTurbineJSSetting() + turbineJSBinary := fmt.Sprintf("@meroxa/turbine-js-cli@%s", turbineJSVersion) + if shouldUseLocalTurbineJS == isTrue { + turbineJSBinary = "turbine-js-cli" + } + args := []string{"npx", "--yes", turbineJSBinary} + args = append(args, params...) + return args +} + +func executeTurbineJSCommand(params []string) *exec.Cmd { + return exec.Command(params[0], params[1:]...) //nolint:gosec +} diff --git a/cmd/meroxa/turbine_cli/utils_test.go b/cmd/meroxa/turbine/javascript/utils_test.go similarity index 98% rename from cmd/meroxa/turbine_cli/utils_test.go rename to cmd/meroxa/turbine/javascript/utils_test.go index 2031e349b..b1b52c521 100644 --- a/cmd/meroxa/turbine_cli/utils_test.go +++ b/cmd/meroxa/turbine/javascript/utils_test.go @@ -1,4 +1,4 @@ -package turbinecli +package turbinejs import ( "fmt" diff --git a/cmd/meroxa/turbine_cli/local_deployment.go b/cmd/meroxa/turbine/local_deployment.go similarity index 99% rename from cmd/meroxa/turbine_cli/local_deployment.go rename to cmd/meroxa/turbine/local_deployment.go index c1655fb2c..cf93649c2 100644 --- a/cmd/meroxa/turbine_cli/local_deployment.go +++ b/cmd/meroxa/turbine/local_deployment.go @@ -1,4 +1,4 @@ -package turbinecli +package turbine import ( "context" @@ -12,12 +12,12 @@ import ( "strings" "time" - "github.com/docker/docker/pkg/archive" - turbine "github.com/meroxa/turbine-go/deploy" - "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/archive" + "github.com/meroxa/cli/log" + turbine "github.com/meroxa/turbine-go/deploy" ) type LocalDeploy struct { diff --git a/cmd/meroxa/turbine/mock/cli.go b/cmd/meroxa/turbine/mock/cli.go new file mode 100644 index 000000000..2a6857d7e --- /dev/null +++ b/cmd/meroxa/turbine/mock/cli.go @@ -0,0 +1,48 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: cmd/meroxa/turbine/interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockCLI is a mock of CLI interface. +type MockCLI struct { + ctrl *gomock.Controller + recorder *MockCLIMockRecorder +} + +// MockCLIMockRecorder is the mock recorder for MockCLI. +type MockCLIMockRecorder struct { + mock *MockCLI +} + +// NewMockCLI creates a new mock instance. +func NewMockCLI(ctrl *gomock.Controller) *MockCLI { + mock := &MockCLI{ctrl: ctrl} + mock.recorder = &MockCLIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCLI) EXPECT() *MockCLIMockRecorder { + return m.recorder +} + +// Upgrade mocks base method. +func (m *MockCLI) Upgrade(appPath string, vendor bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Upgrade", appPath, vendor) + ret0, _ := ret[0].(error) + return ret0 +} + +// Upgrade indicates an expected call of Upgrade. +func (mr *MockCLIMockRecorder) Upgrade(appPath, vendor interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upgrade", reflect.TypeOf((*MockCLI)(nil).Upgrade), appPath, vendor) +} diff --git a/cmd/meroxa/turbine/python/cli.go b/cmd/meroxa/turbine/python/cli.go new file mode 100644 index 000000000..6ec5e2ed9 --- /dev/null +++ b/cmd/meroxa/turbine/python/cli.go @@ -0,0 +1,14 @@ +package turbinepy + +import ( + "github.com/meroxa/cli/cmd/meroxa/turbine" + "github.com/meroxa/cli/log" +) + +type turbinePyCLI struct { + logger log.Logger +} + +func New(l log.Logger) turbine.CLI { + return &turbinePyCLI{logger: l} +} diff --git a/cmd/meroxa/turbine_cli/python/deploy.go b/cmd/meroxa/turbine/python/deploy.go similarity index 90% rename from cmd/meroxa/turbine_cli/python/deploy.go rename to cmd/meroxa/turbine/python/deploy.go index e200191f6..4076fdee8 100644 --- a/cmd/meroxa/turbine_cli/python/deploy.go +++ b/cmd/meroxa/turbine/python/deploy.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/meroxa/cli/cmd/meroxa/global" - turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) @@ -71,7 +71,7 @@ func RunDeployApp(ctx context.Context, l log.Logger, path, imageName, appName, g cmd.Env = os.Environ() cmd.Env = append(cmd.Env, fmt.Sprintf("MEROXA_ACCESS_TOKEN=%s", accessToken)) - _, err = turbinecli.RunCmdWithErrorDetection(ctx, cmd, l) + _, err = turbine.RunCmdWithErrorDetection(ctx, cmd, l) return err } @@ -86,8 +86,8 @@ func CleanUpApp(path string) (string, error) { } // GetResources asks turbine for a list of resources used by the given app. -func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbinecli.ApplicationResource, error) { - var resources []turbinecli.ApplicationResource +func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([]turbine.ApplicationResource, error) { + var resources []turbine.ApplicationResource cmd := exec.Command("turbine-py", "listResources", appPath) output, err := cmd.CombinedOutput() @@ -97,7 +97,7 @@ func GetResources(ctx context.Context, l log.Logger, appPath, appName string) ([ if err := json.Unmarshal(output, &resources); err != nil { // fall back if not json - return turbinecli.GetResourceNamesFromString(string(output)), nil + return turbine.GetResourceNamesFromString(string(output)), nil } return resources, nil diff --git a/cmd/meroxa/turbine_cli/python/init.go b/cmd/meroxa/turbine/python/init.go similarity index 65% rename from cmd/meroxa/turbine_cli/python/init.go rename to cmd/meroxa/turbine/python/init.go index f56ba8ac4..627eeabd9 100644 --- a/cmd/meroxa/turbine_cli/python/init.go +++ b/cmd/meroxa/turbine/python/init.go @@ -4,12 +4,12 @@ import ( "context" "os/exec" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + utils "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) func Init(ctx context.Context, l log.Logger, name, path string) error { cmd := exec.Command("turbine-py", "generate", name, path) - _, err := turbineCLI.RunCmdWithErrorDetection(ctx, cmd, l) + _, err := utils.RunCmdWithErrorDetection(ctx, cmd, l) return err } diff --git a/cmd/meroxa/turbine_cli/python/run.go b/cmd/meroxa/turbine/python/run.go similarity index 65% rename from cmd/meroxa/turbine_cli/python/run.go rename to cmd/meroxa/turbine/python/run.go index 54c8e7e7f..2f99acb76 100644 --- a/cmd/meroxa/turbine_cli/python/run.go +++ b/cmd/meroxa/turbine/python/run.go @@ -4,13 +4,13 @@ import ( "context" "os/exec" - turbineCLI "github.com/meroxa/cli/cmd/meroxa/turbine_cli" + utils "github.com/meroxa/cli/cmd/meroxa/turbine" "github.com/meroxa/cli/log" ) func Run(ctx context.Context, l log.Logger, path string) error { cmd := exec.Command("turbine-py", "run", path) - stdOut, err := turbineCLI.RunCmdWithErrorDetection(ctx, cmd, l) + stdOut, err := utils.RunCmdWithErrorDetection(ctx, cmd, l) l.Info(ctx, stdOut) return err } diff --git a/cmd/meroxa/turbine/python/upgrade.go b/cmd/meroxa/turbine/python/upgrade.go new file mode 100644 index 000000000..b1184d9a8 --- /dev/null +++ b/cmd/meroxa/turbine/python/upgrade.go @@ -0,0 +1,44 @@ +package turbinepy + +import ( + "fmt" + "os/exec" + + "github.com/meroxa/cli/log" +) + +const turbinePYVersion = "1.1.0" + +// Upgrade fetches the latest Meroxa dependencies. +func (t *turbinePyCLI) Upgrade(appPath string, vendor bool) error { + cmd := exec.Command("grep", "turbine-py==", "requirements.txt") + cmd.Dir = appPath + err := cmd.Run() + if err != nil { + t.logger.StartSpinner("\t", " Tidying up requirements.txt...") + cmd = exec.Command("bash", "-c", "sed -i 's+meroxa-py++g' requirements.txt") + cmd.Dir = appPath + err1 := cmd.Run() + + replace := fmt.Sprintf("'s+turbine-py+turbine-py==%s+g'", turbinePYVersion) + cmd = exec.Command("bash", "-c", "sed -i "+replace+" requirements.txt") + cmd.Dir = appPath + err2 := cmd.Run() + if err1 == nil && err2 == nil { + t.logger.StopSpinnerWithStatus("Tidied up requirements.txt successfully!", log.Successful) + } else { + t.logger.StopSpinnerWithStatus("Issues encountered tidying up requirements.txt. Moving on...", log.Failed) + } + } + + t.logger.StartSpinner("\t", " Updating Python dependencies...") + cmd = exec.Command("pip", "install", "turbine-py", "-U") + cmd.Dir = appPath + out, err := cmd.CombinedOutput() + if err != nil { + t.logger.StopSpinnerWithStatus("\t", log.Failed) + return fmt.Errorf(string(out)) + } + t.logger.StopSpinnerWithStatus("Updated Python dependencies successfully!", log.Successful) + return nil +} diff --git a/cmd/meroxa/turbine_cli/utils.go b/cmd/meroxa/turbine/utils.go similarity index 61% rename from cmd/meroxa/turbine_cli/utils.go rename to cmd/meroxa/turbine/utils.go index b1eecd21f..42f62285e 100644 --- a/cmd/meroxa/turbine_cli/utils.go +++ b/cmd/meroxa/turbine/utils.go @@ -1,4 +1,4 @@ -package turbinecli +package turbine import ( "archive/tar" @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "go/build" "io" "os" "os/exec" @@ -17,12 +16,9 @@ import ( "regexp" "strings" - "github.com/meroxa/cli/cmd/meroxa/global" "github.com/meroxa/cli/log" ) -const turbineJSVersion = "1.0.0" - type AppConfig struct { Name string `json:"name"` Environment string `json:"environment"` @@ -33,7 +29,6 @@ type AppConfig struct { } var prefetched *AppConfig -var isTrue = "true" type ApplicationResource struct { Name string `json:"name"` @@ -74,25 +69,10 @@ func GetPath(flag string) (string, error) { return flag, nil } -// GetLang will return language defined either by `--lang` or the one defined by user in the app.json. -func GetLang(ctx context.Context, l log.Logger, flag, pwd string) (string, error) { - if flag != "" { - return flag, nil - } - - lang, err := GetLangFromAppJSON(ctx, l, pwd) - if err != nil { - return "", err - } else if lang == "" { - return "", fmt.Errorf("flag --lang is required unless lang is specified in your app.json") - } - return lang, nil -} - // GetLangFromAppJSON returns specified language in users' app.json. func GetLangFromAppJSON(ctx context.Context, l log.Logger, pwd string) (string, error) { l.StartSpinner("\t", " Determining application language from app.json...") - appConfig, err := readConfigFile(pwd) + appConfig, err := ReadConfigFile(pwd) if err != nil { l.StopSpinnerWithStatus("Something went wrong reading your app.json", log.Failed) return "", err @@ -108,8 +88,8 @@ func GetLangFromAppJSON(ctx context.Context, l log.Logger, pwd string) (string, // GetAppNameFromAppJSON returns specified app name in users' app.json. func GetAppNameFromAppJSON(ctx context.Context, l log.Logger, pwd string) (string, error) { - l.StartSpinner("\t", "Reading application name from app.json...") - appConfig, err := readConfigFile(pwd) + l.StartSpinner("\t", " Reading application name from app.json...") + appConfig, err := ReadConfigFile(pwd) if err != nil { return "", err } @@ -124,37 +104,37 @@ func GetAppNameFromAppJSON(ctx context.Context, l log.Logger, pwd string) (strin // SetModuleInitInAppJSON returns whether to run mod init. func SetModuleInitInAppJSON(pwd string, skipInit bool) error { - appConfig, err := readConfigFile(pwd) + appConfig, err := ReadConfigFile(pwd) if err != nil { return err } - appConfig.ModuleInit = isTrue + appConfig.ModuleInit = "true" if skipInit { appConfig.ModuleInit = "false" } - err = writeConfigFile(pwd, appConfig) // will never be programmatically read again, but a marker of what turbine did + err = WriteConfigFile(pwd, appConfig) return err } // SetVendorInAppJSON returns whether to vendor modules. func SetVendorInAppJSON(pwd string, vendor bool) error { - appConfig, err := readConfigFile(pwd) + appConfig, err := ReadConfigFile(pwd) if err != nil { return err } appConfig.Vendor = "false" if vendor { - appConfig.Vendor = isTrue + appConfig.Vendor = "true" } - err = writeConfigFile(pwd, appConfig) // will never be programmatically read again, but a marker of what turbine did + err = WriteConfigFile(pwd, appConfig) return err } -// readConfigFile will read the content of an app.json based on path. -func readConfigFile(appPath string) (AppConfig, error) { +// ReadConfigFile will read the content of an app.json based on path. +func ReadConfigFile(appPath string) (AppConfig, error) { var appConfig AppConfig - if prefetched == nil { + if prefetched == nil || os.Getenv("UNIT_TEST") != "" { appConfigPath := path.Join(appPath, "app.json") appConfigBytes, err := os.ReadFile(appConfigPath) if err != nil { @@ -170,7 +150,7 @@ func readConfigFile(appPath string) (AppConfig, error) { return *prefetched, nil } -func writeConfigFile(appPath string, cfg AppConfig) error { +func WriteConfigFile(appPath string, cfg AppConfig) error { appConfigPath := path.Join(appPath, "app.json") data, err := json.MarshalIndent(cfg, "", " ") if err != nil { @@ -209,7 +189,7 @@ func GitChecks(ctx context.Context, l log.Logger, appPath string) error { // ValidateBranch validates the deployment is being performed from one of the allowed branches. func ValidateBranch(ctx context.Context, l log.Logger, appPath string) error { - l.StartSpinner("", "Validating branch...") + l.StartSpinner("", " Validating branch...") branchName, err := GetGitBranch(appPath) if err != nil { return err @@ -256,104 +236,8 @@ func GetGitBranch(appPath string) (string, error) { return strings.TrimSpace(string(output)), nil } -func GoInit(ctx context.Context, l log.Logger, appPath string, skipInit, vendor bool) error { - l.StartSpinner("\t", "Running golang module initializing...") - skipLog := "skipping go module initialization\n\tFor guidance, visit " + - "https://docs.meroxa.com/beta-overview#go-mod-init-for-a-new-golang-turbine-data-application" - - goPath := os.Getenv("GOPATH") - if goPath == "" { - goPath = build.Default.GOPATH - } - if goPath == "" { - l.StopSpinnerWithStatus("$GOPATH not set up; "+skipLog, log.Warning) - return nil - } - i := strings.Index(appPath, goPath+"/src") - if i == -1 || i != 0 { - l.StopSpinnerWithStatus(fmt.Sprintf("%s is not under $GOPATH/src; %s", appPath, skipLog), log.Warning) - return nil - } - - // temporarily switching to the app's directory - pwd, err := switchToAppDirectory(appPath) - if err != nil { - l.StopSpinnerWithStatus("\t", log.Failed) - return err - } - - // initialize the user's module - err = SetModuleInitInAppJSON(appPath, skipInit) - if err != nil { - l.StopSpinnerWithStatus("\t", log.Failed) - return err - } - - err = modulesInit(l, appPath, skipInit, vendor) - if err != nil { - l.StopSpinnerWithStatus("\t", log.Failed) - return err - } - - return os.Chdir(pwd) -} - -func modulesInit(l log.Logger, appPath string, skipInit, vendor bool) error { - if skipInit { - return nil - } - - cmd := exec.Command("go", "mod", "init") - output, err := cmd.CombinedOutput() - if err != nil { - l.StopSpinnerWithStatus(fmt.Sprintf("\t%s", string(output)), log.Failed) - return err - } - successLog := "go mod init succeeded" - goPath := os.Getenv("GOPATH") - if goPath == "" { - successLog += fmt.Sprintf(" (while assuming GOPATH is %s)", build.Default.GOPATH) - } - l.StopSpinnerWithStatus(successLog+"!", log.Successful) - l.StartSpinner("\t", "Getting latest turbine-go and turbine-go/running dependencies...") - cmd = exec.Command("go", "get", "github.com/meroxa/turbine-go") - output, err = cmd.CombinedOutput() - if err != nil { - l.StopSpinnerWithStatus(fmt.Sprintf("\t%s", string(output)), log.Failed) - return err - } - cmd = exec.Command("go", "get", "github.com/meroxa/turbine-go/runner") - output, err = cmd.CombinedOutput() - if err != nil { - l.StopSpinnerWithStatus(fmt.Sprintf("\t%s", string(output)), log.Failed) - return err - } - l.StopSpinnerWithStatus("Downloaded latest turbine-go and turbine-go/running dependencies successfully!", log.Successful) - - // download dependencies - err = SetVendorInAppJSON(appPath, vendor) - if err != nil { - return err - } - depsLog := "Downloading dependencies" - cmd = exec.Command("go", "mod", "download") - if vendor { - depsLog += " to vendor" - cmd = exec.Command("go", "mod", "vendor") - } - depsLog += "..." - l.StartSpinner("\t", depsLog) - output, err = cmd.CombinedOutput() - if err != nil { - l.StopSpinnerWithStatus(fmt.Sprintf("\t%s", string(output)), log.Failed) - return err - } - l.StopSpinnerWithStatus("Downloaded all other dependencies successfully!", log.Successful) - return nil -} - -// switchToAppDirectory switches temporarily to the application's directory. -func switchToAppDirectory(appPath string) (string, error) { +// SwitchToAppDirectory switches temporarily to the application's directory. +func SwitchToAppDirectory(appPath string) (string, error) { pwd, err := os.Getwd() if err != nil { return pwd, err @@ -394,7 +278,7 @@ func CreateTarAndZipFile(src string, buf io.Writer) error { appDir := filepath.Base(src) // Change to parent's app directory - pwd, err := switchToAppDirectory(filepath.Dir(src)) + pwd, err := SwitchToAppDirectory(filepath.Dir(src)) if err != nil { return err } @@ -450,26 +334,6 @@ func CreateTarAndZipFile(src string, buf io.Writer) error { return os.Chdir(pwd) } -func RunTurbineJS(params ...string) (cmd *exec.Cmd) { - args := getTurbineJSBinary(params) - return executeTurbineJSCommand(args) -} - -func getTurbineJSBinary(params []string) []string { - shouldUseLocalTurbineJS := global.GetLocalTurbineJSSetting() - turbineJSBinary := fmt.Sprintf("@meroxa/turbine-js-cli@%s", turbineJSVersion) - if shouldUseLocalTurbineJS == isTrue { - turbineJSBinary = "turbine-js-cli" - } - args := []string{"npx", "--yes", turbineJSBinary} - args = append(args, params...) - return args -} - -func executeTurbineJSCommand(params []string) *exec.Cmd { - return exec.Command(params[0], params[1:]...) //nolint:gosec -} - func shouldSkipDir(fi os.FileInfo) bool { if !fi.IsDir() { return false diff --git a/cmd/meroxa/turbine_cli/javascript/init.go b/cmd/meroxa/turbine_cli/javascript/init.go deleted file mode 100644 index 37e289c02..000000000 --- a/cmd/meroxa/turbine_cli/javascript/init.go +++ /dev/null @@ -1,14 +0,0 @@ -package turbinejs - -import ( - "context" - - turbinecli "github.com/meroxa/cli/cmd/meroxa/turbine_cli" - "github.com/meroxa/cli/log" -) - -func Init(ctx context.Context, l log.Logger, name, path string) error { - cmd := turbinecli.RunTurbineJS("generate", name, path) - _, err := turbinecli.RunCmdWithErrorDetection(ctx, cmd, l) - return err -} diff --git a/docs/cmd/md/meroxa_apps.md b/docs/cmd/md/meroxa_apps.md index 593eeddf7..9433f05a2 100644 --- a/docs/cmd/md/meroxa_apps.md +++ b/docs/cmd/md/meroxa_apps.md @@ -27,4 +27,5 @@ Manage Turbine Data Applications (Beta) * [meroxa apps logs](meroxa_apps_logs.md) - View relevant logs to the state of the given Turbine Data Application (Beta) * [meroxa apps remove](meroxa_apps_remove.md) - Removes a Turbine Data Application (Beta) * [meroxa apps run](meroxa_apps_run.md) - Execute a Turbine Data Application locally (Beta) +* [meroxa apps upgrade](meroxa_apps_upgrade.md) - Upgrade a Turbine Data Application (Beta) diff --git a/docs/cmd/md/meroxa_apps_run.md b/docs/cmd/md/meroxa_apps_run.md index bcdded0fc..b7c977b73 100644 --- a/docs/cmd/md/meroxa_apps_run.md +++ b/docs/cmd/md/meroxa_apps_run.md @@ -22,7 +22,6 @@ meroxa apps run --path ../go-demo # it'll use lang defined in your app.json ``` -h, --help help for run - -l, --lang string language to use (go | js) --path string path of application to run ``` diff --git a/docs/cmd/md/meroxa_apps_upgrade.md b/docs/cmd/md/meroxa_apps_upgrade.md new file mode 100644 index 000000000..6e9e5a980 --- /dev/null +++ b/docs/cmd/md/meroxa_apps_upgrade.md @@ -0,0 +1,34 @@ +## meroxa apps upgrade + +Upgrade a Turbine Data Application (Beta) + +``` +meroxa apps upgrade [APP_NAME] [--path pwd] [flags] +``` + +### Examples + +``` +meroxa apps upgrade my-app --path ~/code +``` + +### Options + +``` + -h, --help help for upgrade + --path string path where application exists (current directory as default) +``` + +### Options inherited from parent commands + +``` + --cli-config-file string meroxa configuration file + --debug display any debugging information + --json output json + --timeout duration set the duration of the client timeout in seconds (default 10s) +``` + +### SEE ALSO + +* [meroxa apps](meroxa_apps.md) - Manage Turbine Data Applications (Beta) + diff --git a/docs/cmd/www/meroxa-apps-run.md b/docs/cmd/www/meroxa-apps-run.md index 6eca2feee..22703903f 100644 --- a/docs/cmd/www/meroxa-apps-run.md +++ b/docs/cmd/www/meroxa-apps-run.md @@ -29,7 +29,6 @@ meroxa apps run --path ../go-demo # it'll use lang defined in your app.json ``` -h, --help help for run - -l, --lang string language to use (go | js) --path string path of application to run ``` diff --git a/docs/cmd/www/meroxa-apps-upgrade.md b/docs/cmd/www/meroxa-apps-upgrade.md new file mode 100644 index 000000000..7078c16c0 --- /dev/null +++ b/docs/cmd/www/meroxa-apps-upgrade.md @@ -0,0 +1,41 @@ +--- +createdAt: +updatedAt: +title: "meroxa apps upgrade" +slug: meroxa-apps-upgrade +url: /cli/cmd/meroxa-apps-upgrade/ +--- +## meroxa apps upgrade + +Upgrade a Turbine Data Application (Beta) + +``` +meroxa apps upgrade [APP_NAME] [--path pwd] [flags] +``` + +### Examples + +``` +meroxa apps upgrade my-app --path ~/code +``` + +### Options + +``` + -h, --help help for upgrade + --path string path where application exists (current directory as default) +``` + +### Options inherited from parent commands + +``` + --cli-config-file string meroxa configuration file + --debug display any debugging information + --json output json + --timeout duration set the duration of the client timeout in seconds (default 10s) +``` + +### SEE ALSO + +* [meroxa apps](/cli/cmd/meroxa-apps/) - Manage Turbine Data Applications (Beta) + diff --git a/docs/cmd/www/meroxa-apps.md b/docs/cmd/www/meroxa-apps.md index 194f20f1f..71ed703f4 100644 --- a/docs/cmd/www/meroxa-apps.md +++ b/docs/cmd/www/meroxa-apps.md @@ -34,4 +34,5 @@ Manage Turbine Data Applications (Beta) * [meroxa apps logs](/cli/cmd/meroxa-apps-logs/) - View relevant logs to the state of the given Turbine Data Application (Beta) * [meroxa apps remove](/cli/cmd/meroxa-apps-remove/) - Removes a Turbine Data Application (Beta) * [meroxa apps run](/cli/cmd/meroxa-apps-run/) - Execute a Turbine Data Application locally (Beta) +* [meroxa apps upgrade](/cli/cmd/meroxa-apps-upgrade/) - Upgrade a Turbine Data Application (Beta) diff --git a/etc/completion/meroxa.completion.sh b/etc/completion/meroxa.completion.sh index 4f29b8237..502bf3e31 100644 --- a/etc/completion/meroxa.completion.sh +++ b/etc/completion/meroxa.completion.sh @@ -606,9 +606,36 @@ _meroxa_apps_run() flags+=("--help") flags+=("-h") - flags+=("--lang=") - two_word_flags+=("--lang") - two_word_flags+=("-l") + flags+=("--path=") + two_word_flags+=("--path") + flags+=("--cli-config-file=") + two_word_flags+=("--cli-config-file") + flags+=("--debug") + flags+=("--json") + flags+=("--timeout=") + two_word_flags+=("--timeout") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_meroxa_apps_upgrade() +{ + last_command="meroxa_apps_upgrade" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--help") + flags+=("-h") flags+=("--path=") two_word_flags+=("--path") flags+=("--cli-config-file=") @@ -652,6 +679,7 @@ _meroxa_apps() aliashash["rm"]="remove" fi commands+=("run") + commands+=("upgrade") flags=() two_word_flags=() diff --git a/etc/man/man1/meroxa-apps-run.1 b/etc/man/man1/meroxa-apps-run.1 index 8f86c9550..c6040a09a 100644 --- a/etc/man/man1/meroxa-apps-run.1 +++ b/etc/man/man1/meroxa-apps-run.1 @@ -21,10 +21,6 @@ meroxa apps run will build your app locally to then run it locally in \-\-path. \fB\-h\fP, \fB\-\-help\fP[=false] help for run -.PP -\fB\-l\fP, \fB\-\-lang\fP="" - language to use (go | js) - .PP \fB\-\-path\fP="" path of application to run diff --git a/etc/man/man1/meroxa-apps-upgrade.1 b/etc/man/man1/meroxa-apps-upgrade.1 new file mode 100644 index 000000000..aa0861631 --- /dev/null +++ b/etc/man/man1/meroxa-apps-upgrade.1 @@ -0,0 +1,60 @@ +.nh +.TH "Meroxa" "1" "Aug 2022" "Meroxa CLI " "Meroxa Manual" + +.SH NAME +.PP +meroxa\-apps\-upgrade \- Upgrade a Turbine Data Application (Beta) + + +.SH SYNOPSIS +.PP +\fBmeroxa apps upgrade [APP\_NAME] [\-\-path pwd] [flags]\fP + + +.SH DESCRIPTION +.PP +Upgrade a Turbine Data Application (Beta) + + +.SH OPTIONS +.PP +\fB\-h\fP, \fB\-\-help\fP[=false] + help for upgrade + +.PP +\fB\-\-path\fP="" + path where application exists (current directory as default) + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-cli\-config\-file\fP="" + meroxa configuration file + +.PP +\fB\-\-debug\fP[=false] + display any debugging information + +.PP +\fB\-\-json\fP[=false] + output json + +.PP +\fB\-\-timeout\fP=10s + set the duration of the client timeout in seconds + + +.SH EXAMPLE +.PP +.RS + +.nf +meroxa apps upgrade my\-app \-\-path \~/code + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBmeroxa\-apps(1)\fP diff --git a/etc/man/man1/meroxa-apps.1 b/etc/man/man1/meroxa-apps.1 index 90b3abc55..be0de270d 100644 --- a/etc/man/man1/meroxa-apps.1 +++ b/etc/man/man1/meroxa-apps.1 @@ -42,4 +42,4 @@ Manage Turbine Data Applications (Beta) .SH SEE ALSO .PP -\fBmeroxa(1)\fP, \fBmeroxa\-apps\-deploy(1)\fP, \fBmeroxa\-apps\-describe(1)\fP, \fBmeroxa\-apps\-init(1)\fP, \fBmeroxa\-apps\-list(1)\fP, \fBmeroxa\-apps\-logs(1)\fP, \fBmeroxa\-apps\-remove(1)\fP, \fBmeroxa\-apps\-run(1)\fP +\fBmeroxa(1)\fP, \fBmeroxa\-apps\-deploy(1)\fP, \fBmeroxa\-apps\-describe(1)\fP, \fBmeroxa\-apps\-init(1)\fP, \fBmeroxa\-apps\-list(1)\fP, \fBmeroxa\-apps\-logs(1)\fP, \fBmeroxa\-apps\-remove(1)\fP, \fBmeroxa\-apps\-run(1)\fP, \fBmeroxa\-apps\-upgrade(1)\fP diff --git a/log/log.go b/log/log.go index 7e2519611..eed7102ba 100644 --- a/log/log.go +++ b/log/log.go @@ -1,5 +1,7 @@ package log +import "os" + type Logger interface { LeveledLogger JSONLogger @@ -19,3 +21,8 @@ func New(l1 LeveledLogger, l2 JSONLogger, l3 SpinnerLogger) Logger { SpinnerLogger: l3, } } + +func NewWithDevNull() Logger { + o, _ := os.Open(os.DevNull) + return New(NewLeveledLogger(o, Debug), NewJSONLogger(o), NewSpinnerLogger(o)) +} diff --git a/log/spinner.go b/log/spinner.go index d13edc1b5..32d7b144d 100644 --- a/log/spinner.go +++ b/log/spinner.go @@ -26,16 +26,17 @@ type SpinnerLogger interface { } func NewSpinnerLogger(out io.Writer) SpinnerLogger { - return &spinnerLogger{l: log.New(out, "", 0)} + return &spinnerLogger{l: log.New(out, "", 0), out: out} } type spinnerLogger struct { - l *log.Logger - s *spinner.Spinner + l *log.Logger + s *spinner.Spinner + out io.Writer } func (l *spinnerLogger) StartSpinner(prefix, suffix string) { - l.s = spinner.New(spinner.CharSets[14], 100*time.Millisecond) //nolint:gomnd + l.s = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(l.out)) //nolint:gomnd l.s.Prefix = prefix l.s.Suffix = suffix l.s.Start()