From 0f51a9b64258176aa2ed33bd0ae8b0ac9c827a44 Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Tue, 28 Jan 2025 23:49:04 +1100 Subject: [PATCH 1/3] wip preview command --- README.md | 2 + cmd/stack.go | 444 ++++++++++++++++-- go.mod | 28 +- go.sum | 59 +-- pkg/provider/client.go | 43 ++ .../commands/stack/preview/stack_preview.go | 286 +++++++++++ 6 files changed, 769 insertions(+), 93 deletions(-) create mode 100644 pkg/view/tui/commands/stack/preview/stack_preview.go diff --git a/README.md b/README.md index 0c73cd625..b3936ff56 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ Documentation for all available commands: (alias: nitric down) - nitric stack list : List all stacks in the project - nitric stack new [stackName] [providerName] : Create a new Nitric stack +- nitric stack preview [-s stack] : Preview the updates for a deployed stack + (alias: nitric preview) - nitric stack update [-s stack] : Create or update a deployed stack (alias: nitric up) - nitric start : Run nitric services locally for development and testing diff --git a/cmd/stack.go b/cmd/stack.go index 276394605..c59e8eb29 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -41,9 +41,10 @@ import ( "github.com/nitrictech/cli/pkg/view/tui/commands/build" stack_down "github.com/nitrictech/cli/pkg/view/tui/commands/stack/down" stack_new "github.com/nitrictech/cli/pkg/view/tui/commands/stack/new" + stack_preview "github.com/nitrictech/cli/pkg/view/tui/commands/stack/preview" stack_select "github.com/nitrictech/cli/pkg/view/tui/commands/stack/select" - stack_up "github.com/nitrictech/cli/pkg/view/tui/commands/stack/up" "github.com/nitrictech/cli/pkg/view/tui/components/list" + "github.com/nitrictech/cli/pkg/view/tui/components/listprompt" "github.com/nitrictech/cli/pkg/view/tui/components/view" "github.com/nitrictech/cli/pkg/view/tui/teax" deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" @@ -56,6 +57,7 @@ var ( noBuilder bool forceNewStack bool envFile string + skipPreview bool ) var stackCmd = &cobra.Command{ @@ -303,13 +305,296 @@ var stackUpdateCmd = &cobra.Command{ attributesStruct, err := structpb.NewStruct(attributes) tui.CheckErr(err) - eventChan, errorChan := deploymentClient.Up(&deploymentspb.DeploymentUpRequest{ - Spec: spec, + if !skipPreview { + eventChan, errorChan := deploymentClient.Preview(&deploymentspb.DeploymentPreviewRequest{ + Spec: spec, + Attributes: attributesStruct, + Interactive: true, + }) + + // Step 5b. Communicate with server to get preview + if isNonInteractive() { + providerErrorDetected := false + + fmt.Printf("Previewing %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) + go func() { + for update := range errorChan { + fmt.Printf("Error: %s\n", update) + providerErrorDetected = true + } + }() + + go func() { + for outMessage := range providerStdout { + fmt.Printf("%s: %s\n", stackConfig.Provider, outMessage) + } + }() + + // non-interactive environment + for update := range eventChan { + switch content := update.Content.(type) { + case *deploymentspb.DeploymentPreviewEvent_Message: + fmt.Printf("%s\n", content.Message) + case *deploymentspb.DeploymentPreviewEvent_Update: + updateResType := "" + updateResName := "" + if content.Update.Id != nil { + updateResType = content.Update.Id.Type.String() + updateResName = content.Update.Id.Name + } + + if updateResType == "" { + updateResType = "Stack" + } + if updateResName == "" { + updateResName = stackConfig.Name + } + if content.Update.SubResource != "" { + updateResName = fmt.Sprintf("%s:%s", updateResName, content.Update.SubResource) + } + + fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) + case *deploymentspb.DeploymentPreviewEvent_Result: + fmt.Printf("\nResult: %s\n", content.Result.GetText()) + } + } + + // ensure the process exits with a non-zero status code after all messages are processed + if providerErrorDetected { + + } + } else { + // interactive environment + // Step 5c. Start the stack preview view\ + stackPreview := stack_preview.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) + _, err = teax.NewProgram(stackPreview).Run() + } + } + + // Check if the thing is active + + confirmModel := listprompt.NewListPrompt(listprompt.ListPromptArgs{ + Items: list.StringsToListItems([]string{"confirm deployment", "cancel"}), + Tag: "deploy", + Prompt: "Finished preview. Would you like to perform the deployment?", + }) + + selection, err := teax.NewProgram(confirmModel).Run() + tui.CheckErr(err) + + v := view.New() + v.Break() + fmt.Println("") + + stackSelection = selection.(listprompt.ListPrompt).Choice() + if stackSelection == "cancel" { + v.Addln("Cancelling deployment") + fmt.Println(v.Render()) + return + } + + v.Addln("Deployed") + fmt.Println(v.Render()) + + // eventChan, errorChan := deploymentClient.Up(&deploymentspb.DeploymentUpRequest{ + // Spec: spec, + // Attributes: attributesStruct, + // Interactive: true, + // }) + + // // Step 5d. Communicate with server to share progress of update + // if isNonInteractive() { + // providerErrorDetected := false + + // fmt.Printf("Deploying %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) + // go func() { + // for update := range errorChan { + // fmt.Printf("Error: %s\n", update) + // providerErrorDetected = true + // } + // }() + + // go func() { + // for outMessage := range providerStdout { + // fmt.Printf("%s: %s\n", stackConfig.Provider, outMessage) + // } + // }() + + // // non-interactive environment + // for update := range eventChan { + // switch content := update.Content.(type) { + // case *deploymentspb.DeploymentUpEvent_Message: + // fmt.Printf("%s\n", content.Message) + // case *deploymentspb.DeploymentUpEvent_Update: + // updateResType := "" + // updateResName := "" + // if content.Update.Id != nil { + // updateResType = content.Update.Id.Type.String() + // updateResName = content.Update.Id.Name + // } + + // if updateResType == "" { + // updateResType = "Stack" + // } + // if updateResName == "" { + // updateResName = stackConfig.Name + // } + // if content.Update.SubResource != "" { + // updateResName = fmt.Sprintf("%s:%s", updateResName, content.Update.SubResource) + // } + + // fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) + // case *deploymentspb.DeploymentUpEvent_Result: + // fmt.Printf("\nResult: %s\n", content.Result.GetText()) + // } + // } + + // // ensure the process exits with a non-zero status code after all messages are processed + // if providerErrorDetected { + // os.Exit(1) + // } + // } else { + // // interactive environment + // // Step 5e. Start the stack up view + // stackUp := stack_up.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) + // _, err = teax.NewProgram(stackUp).Run() + // tui.CheckErr(err) + // } + }, + Args: cobra.MinimumNArgs(0), + Aliases: []string{"up"}, +} + +var stackDeleteCmd = &cobra.Command{ + Use: "down [-s stack]", + Short: "Undeploy a previously deployed stack, deleting resources", + Long: `Undeploy a previously deployed stack, deleting resources`, + Example: `nitric stack down -s aws + +# To not be prompted, use -y +nitric stack down -s aws -y`, + Run: func(cmd *cobra.Command, args []string) { + fs := afero.NewOsFs() + + stackFiles, err := stack.GetAllStackFiles(fs) + tui.CheckErr(err) + + if len(stackFiles) == 0 { + tui.CheckErr(fmt.Errorf("no stacks found in project root, to create a new one run `nitric stack new`")) + } + + // Step 0. Get the stack file, or prompt if more than 1. + stackSelection := stackFlag + + if isNonInteractive() { + if len(stackFiles) > 1 && stackSelection == "" { + tui.CheckErr(fmt.Errorf("multiple stacks found in project, please specify one with -s")) + } + } + + if stackSelection == "" { + if len(stackFiles) > 1 { + stackList := make([]list.ListItem, len(stackFiles)) + + for i, stackFile := range stackFiles { + stackName, err := stack.GetStackNameFromFileName(stackFile) + tui.CheckErr(err) + stackConfig, err := stack.ConfigFromName[map[string]any](fs, stackName) + tui.CheckErr(err) + stackList[i] = stack_select.StackListItem{ + Name: stackConfig.Name, + Provider: stackConfig.Provider, + } + } + + promptModel := stack_select.New(stack_select.Args{ + Prompt: "Which stack would you like to delete?", + StackList: stackList, + }) + + selection, err := teax.NewProgram(promptModel).Run() + tui.CheckErr(err) + stackSelection = selection.(stack_select.Model).Choice() + if stackSelection == "" { + return + } + } else { + stackSelection, err = stack.GetStackNameFromFileName(stackFiles[0]) + tui.CheckErr(err) + } + } + + stackConfig, err := stack.ConfigFromName[map[string]any](fs, stackSelection) + tui.CheckErr(err) + + if !isNonInteractive() { + _ = pulumi.EnsurePulumiPassphrase(fs) + } + + proj, err := project.FromFile(fs, "") + tui.CheckErr(err) + + // Step 0a. Locate/Download provider where applicable. + prov, err := provider.NewProvider(stackConfig.Provider, proj, fs) + tui.CheckErr(err) + + err = prov.Install() + tui.CheckErr(err) + + providerStdout := make(chan string) + + additionalEnvFiles := []string{} + if envFile != "" { + additionalEnvFiles = append(additionalEnvFiles, envFile) + } + + envVariables, err := env.ReadLocalEnv(additionalEnvFiles...) + if err != nil && os.IsNotExist(err) { + if !os.IsNotExist(err) { + tui.CheckErr(err) + } + // If it doesn't exist set blank + envVariables = map[string]string{} + } + + // Allow Beta providers to be run if 'beta-providers' is enabled in preview flags + if slices.Contains(proj.Preview, preview.Feature_BetaProviders) { + envVariables["NITRIC_BETA_PROVIDERS"] = "true" + } + + // Step 4. Start the deployment provider server + providerAddress, err := prov.Start(&provider.StartOptions{ + Env: envVariables, + StdOut: providerStdout, + StdErr: providerStdout, + }) + tui.CheckErr(err) + + defer func() { + err = prov.Stop() + tui.CheckErr(err) + }() + + // Step 5a. Send specification to provider for deployment + deploymentClient := provider.NewDeploymentClient(providerAddress, true) + + attributes := map[string]interface{}{} + + attributes["stack"] = stackConfig.Name + attributes["project"] = proj.Name + + for k, v := range stackConfig.Config { + attributes[k] = v + } + + attributesStruct, err := structpb.NewStruct(attributes) + tui.CheckErr(err) + + eventChannel, errorChan := deploymentClient.Down(&deploymentspb.DeploymentDownRequest{ Attributes: attributesStruct, Interactive: true, }) - // Step 5b. Communicate with server to share progress of ... if isNonInteractive() { providerErrorDetected := false @@ -328,11 +613,11 @@ var stackUpdateCmd = &cobra.Command{ }() // non-interactive environment - for update := range eventChan { + for update := range eventChannel { switch content := update.Content.(type) { - case *deploymentspb.DeploymentUpEvent_Message: + case *deploymentspb.DeploymentDownEvent_Message: fmt.Printf("%s\n", content.Message) - case *deploymentspb.DeploymentUpEvent_Update: + case *deploymentspb.DeploymentDownEvent_Update: updateResType := "" updateResName := "" if content.Update.Id != nil { @@ -351,8 +636,8 @@ var stackUpdateCmd = &cobra.Command{ } fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) - case *deploymentspb.DeploymentUpEvent_Result: - fmt.Printf("\nResult: %s\n", content.Result.GetText()) + case *deploymentspb.DeploymentDownEvent_Result: + fmt.Println("\nStack down complete") } } @@ -361,25 +646,20 @@ var stackUpdateCmd = &cobra.Command{ os.Exit(1) } } else { - // interactive environment - // Step 5c. Start the stack up view - stackUp := stack_up.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) - _, err = teax.NewProgram(stackUp).Run() + stackDown := stack_down.New(stackConfig.Provider, stackConfig.Name, eventChannel, providerStdout, errorChan) + + _, err = teax.NewProgram(stackDown).Run() tui.CheckErr(err) } }, - Args: cobra.MinimumNArgs(0), - Aliases: []string{"up"}, + Args: cobra.ExactArgs(0), } -var stackDeleteCmd = &cobra.Command{ - Use: "down [-s stack]", - Short: "Undeploy a previously deployed stack, deleting resources", - Long: `Undeploy a previously deployed stack, deleting resources`, - Example: `nitric stack down -s aws - -# To not be prompted, use -y -nitric stack down -s aws -y`, +var stackPreviewCommand = &cobra.Command{ + Use: "preview [-s stack]", + Short: "Preview the updates for a deployed stack", + Long: `Preview the updates a deployed stack`, + Example: `nitric stack preview -s aws`, Run: func(cmd *cobra.Command, args []string) { fs := afero.NewOsFs() @@ -387,7 +667,7 @@ nitric stack down -s aws -y`, tui.CheckErr(err) if len(stackFiles) == 0 { - tui.CheckErr(fmt.Errorf("no stacks found in project root, to create a new one run `nitric stack new`")) + tui.CheckErr(fmt.Errorf("no stacks found in project, to create a new one run `nitric stack new`")) } // Step 0. Get the stack file, or prompt if more than 1. @@ -415,7 +695,7 @@ nitric stack down -s aws -y`, } promptModel := stack_select.New(stack_select.Args{ - Prompt: "Which stack would you like to delete?", + Prompt: "Which stack would you like to preview?", StackList: stackList, }) @@ -438,6 +718,9 @@ nitric stack down -s aws -y`, _ = pulumi.EnsurePulumiPassphrase(fs) } + // print provider version check + update.PrintOutdatedProviderWarning(stackConfig.Provider) + proj, err := project.FromFile(fs, "") tui.CheckErr(err) @@ -448,9 +731,47 @@ nitric stack down -s aws -y`, err = prov.Install() tui.CheckErr(err) - providerStdout := make(chan string) + // Build the Project's Services (Containers) + buildUpdates, err := proj.BuildServices(fs, !noBuilder) + tui.CheckErr(err) + + batchBuildUpdates, err := proj.BuildBatches(fs, !noBuilder) + tui.CheckErr(err) + + allBuildUpdates := lo.FanIn(10, buildUpdates, batchBuildUpdates) + + if isNonInteractive() { + fmt.Println("building project services") + for _, service := range proj.GetServices() { + fmt.Printf("service matched '%s', auto-naming this service '%s'\n", service.GetFilePath(), service.Name) + } + + // non-interactive environment + for update := range allBuildUpdates { + for _, line := range strings.Split(strings.TrimSuffix(update.Message, "\n"), "\n") { + fmt.Printf("%s [%s]: %s\n", update.ServiceName, update.Status, line) + } + } + } else { + prog := teax.NewProgram(build.NewModel(allBuildUpdates, "Building Services")) + // blocks but quits once the above updates channel is closed by the build process + buildModel, err := prog.Run() + tui.CheckErr(err) + if buildModel.(build.Model).Err != nil { + tui.CheckErr(fmt.Errorf("error building services")) + } + } + + // Step 2. Start the collectors and containers (respectively in pairs) + // Step 3. Merge requirements from collectors into a specification + serviceRequirements, err := proj.CollectServicesRequirements() + tui.CheckErr(err) + + batchRequirements, err := proj.CollectBatchRequirements() + tui.CheckErr(err) additionalEnvFiles := []string{} + if envFile != "" { additionalEnvFiles = append(additionalEnvFiles, envFile) } @@ -469,6 +790,38 @@ nitric stack down -s aws -y`, envVariables["NITRIC_BETA_PROVIDERS"] = "true" } + spec, err := collector.ServiceRequirementsToSpec(proj.Name, envVariables, serviceRequirements, batchRequirements) + tui.CheckErr(err) + + migrationImageContexts, err := collector.GetMigrationImageBuildContexts(serviceRequirements, batchRequirements, fs) + tui.CheckErr(err) + // Build images from contexts and provide updates on the builds + + if len(migrationImageContexts) > 0 { + migrationBuildUpdates, err := project.BuildMigrationImages(fs, migrationImageContexts, !noBuilder) + tui.CheckErr(err) + + if isNonInteractive() { + fmt.Println("building project migration images") + // non-interactive environment + for update := range migrationBuildUpdates { + for _, line := range strings.Split(strings.TrimSuffix(update.Message, "\n"), "\n") { + fmt.Printf("%s [%s]: %s\n", update.ServiceName, update.Status, line) + } + } + } else { + prog := teax.NewProgram(build.NewModel(migrationBuildUpdates, "Building Database Migrations")) + // blocks but quits once the above updates channel is closed by the build process + buildModel, err := prog.Run() + tui.CheckErr(err) + if buildModel.(build.Model).Err != nil { + tui.CheckErr(fmt.Errorf("error building services")) + } + } + } + + providerStdout := make(chan string) + // Step 4. Start the deployment provider server providerAddress, err := prov.Start(&provider.StartOptions{ Env: envVariables, @@ -476,9 +829,8 @@ nitric stack down -s aws -y`, StdErr: providerStdout, }) tui.CheckErr(err) - defer func() { - err = prov.Stop() + err := prov.Stop() tui.CheckErr(err) }() @@ -497,15 +849,17 @@ nitric stack down -s aws -y`, attributesStruct, err := structpb.NewStruct(attributes) tui.CheckErr(err) - eventChannel, errorChan := deploymentClient.Down(&deploymentspb.DeploymentDownRequest{ + eventChan, errorChan := deploymentClient.Preview(&deploymentspb.DeploymentPreviewRequest{ + Spec: spec, Attributes: attributesStruct, Interactive: true, }) + // Step 5b. Communicate with server to share progress of ... if isNonInteractive() { providerErrorDetected := false - fmt.Printf("Deploying %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) + fmt.Printf("Previewing %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) go func() { for update := range errorChan { fmt.Printf("Error: %s\n", update) @@ -520,11 +874,11 @@ nitric stack down -s aws -y`, }() // non-interactive environment - for update := range eventChannel { + for update := range eventChan { switch content := update.Content.(type) { - case *deploymentspb.DeploymentDownEvent_Message: + case *deploymentspb.DeploymentPreviewEvent_Message: fmt.Printf("%s\n", content.Message) - case *deploymentspb.DeploymentDownEvent_Update: + case *deploymentspb.DeploymentPreviewEvent_Update: updateResType := "" updateResName := "" if content.Update.Id != nil { @@ -543,8 +897,8 @@ nitric stack down -s aws -y`, } fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) - case *deploymentspb.DeploymentDownEvent_Result: - fmt.Println("\nStack down complete") + case *deploymentspb.DeploymentPreviewEvent_Result: + fmt.Printf("\nResult: %s\n", content.Result.GetText()) } } @@ -553,13 +907,14 @@ nitric stack down -s aws -y`, os.Exit(1) } } else { - stackDown := stack_down.New(stackConfig.Provider, stackConfig.Name, eventChannel, providerStdout, errorChan) - - _, err = teax.NewProgram(stackDown).Run() - tui.CheckErr(err) + // interactive environment + // Step 5c. Start the stack preview view + stackPreview := stack_preview.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) + _, err = teax.NewProgram(stackPreview).Run() } }, - Args: cobra.ExactArgs(0), + Args: cobra.MinimumNArgs(0), + Aliases: []string{"preview"}, } var stackListCmd = &cobra.Command{ @@ -635,6 +990,7 @@ func init() { stackUpdateCmd.Flags().BoolVarP(&noBuilder, "no-builder", "", false, "don't create a buildx container") stackUpdateCmd.Flags().StringVarP(&envFile, "env-file", "e", "", "--env-file config/.my-env") stackUpdateCmd.Flags().BoolVarP(&forceStack, "force", "f", false, "force override previous deployment") + stackUpdateCmd.Flags().BoolVarP(&skipPreview, "skip-preview", "", false, "ignore preview") tui.CheckErr(AddOptions(stackUpdateCmd, false)) // Delete Stack (Down) @@ -642,6 +998,13 @@ func init() { stackDeleteCmd.Flags().BoolVarP(&confirmDown, "yes", "y", false, "confirm the destruction of the stack") tui.CheckErr(AddOptions(stackDeleteCmd, false)) + // Preview Stack (Preview) + stackCmd.AddCommand(tui.AddDependencyCheck(stackPreviewCommand, tui.RequireContainerBuilder)) + stackPreviewCommand.Flags().BoolVarP(&noBuilder, "no-builder", "", false, "don't create a buildx container") + stackPreviewCommand.Flags().StringVarP(&envFile, "env-file", "e", "", "--env-file config/.my-env") + stackPreviewCommand.Flags().BoolVarP(&forceStack, "force", "f", false, "force override previous deployment") + tui.CheckErr(AddOptions(stackPreviewCommand, false)) + // List Stacks stackCmd.AddCommand(stackListCmd) @@ -650,4 +1013,5 @@ func init() { addAlias("stack update", "up", true) addAlias("stack down", "down", true) + addAlias("stack preview", "preview", true) } diff --git a/go.mod b/go.mod index 35254abf2..1cd15d1fc 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.22.1 toolchain go1.23.0 -replace github.com/mattn/go-ieproxy => github.com/darthShadow/go-ieproxy v0.0.0-20220916090656-69928ad83ed6 +replace ( + github.com/mattn/go-ieproxy => github.com/darthShadow/go-ieproxy v0.0.0-20220916090656-69928ad83ed6 + github.com/nitrictech/nitric/core v0.0.0-20241003062412-76ea6275fb0b => ../nitric/core +) require github.com/golangci/golangci-lint v1.61.0 require ( - github.com/AlecAivazis/survey/v2 v2.3.6 github.com/asdine/storm v2.1.2+incompatible github.com/aws/aws-sdk-go v1.44.175 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -28,7 +30,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/valyala/fasthttp v1.55.0 golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect - golang.org/x/mod v0.21.0 // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect google.golang.org/grpc v1.66.0 gopkg.in/yaml.v2 v2.4.0 @@ -52,11 +54,12 @@ require ( github.com/olahol/melody v1.1.3 github.com/robfig/cron/v3 v3.0.1 github.com/samber/lo v1.38.1 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 github.com/stretchr/testify v1.9.0 github.com/wk8/go-ordered-map/v2 v2.1.8 go.etcd.io/bbolt v1.3.6 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.10.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 ) @@ -115,7 +118,7 @@ require ( github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect @@ -173,7 +176,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -197,7 +199,6 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mgechev/revive v1.3.9 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -238,7 +239,6 @@ require ( github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/securego/gosec/v2 v2.21.2 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.10.0 // indirect github.com/sonatard/noctx v0.0.2 // indirect @@ -282,14 +282,14 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.27.0 // indirect google.golang.org/api v0.196.0 // indirect google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/go.sum b/go.sum index 13015e43c..d9d5c9162 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8 github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM= -github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= -github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= @@ -79,14 +77,12 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/Sereal/Sereal v0.0.0-20221130110801-16a4f76670cd h1:rP6LH3aVJTIxgTA3q79sSfnt8DvOlt17IRAklRBN+xo= github.com/Sereal/Sereal v0.0.0-20221130110801-16a4f76670cd/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= -github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= -github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= @@ -179,8 +175,6 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:Yyn github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= @@ -211,8 +205,8 @@ github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQt github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -431,8 +425,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -476,8 +468,6 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= @@ -533,7 +523,6 @@ github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859 github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -557,9 +546,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -599,8 +585,6 @@ github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nitrictech/nitric/cloud/common v0.0.0-20241003062412-76ea6275fb0b h1:wZeUrnmhYjdhSuL6ov+kVfuFJC9H14sk0kzEpt6aRoo= github.com/nitrictech/nitric/cloud/common v0.0.0-20241003062412-76ea6275fb0b/go.mod h1:ZsCdb3xbukhXAp9ZNbV6qWJqRC+eLkxhXy8bhs/cC2A= -github.com/nitrictech/nitric/core v0.0.0-20241003062412-76ea6275fb0b h1:ImQFk66gRM3v9A6qmPImOiV3HJMDAX93X5rplMKn6ok= -github.com/nitrictech/nitric/core v0.0.0-20241003062412-76ea6275fb0b/go.mod h1:9bQnYPqLzq8CcPk5MHT3phg19CWJhDlFOfdIv27lwwM= github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -750,7 +734,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -866,8 +849,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -912,8 +895,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -955,8 +938,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -979,8 +962,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1031,7 +1014,6 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1042,17 +1024,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1063,8 +1044,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1132,8 +1113,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/provider/client.go b/pkg/provider/client.go index 6b81eb0a5..a2b988a78 100644 --- a/pkg/provider/client.go +++ b/pkg/provider/client.go @@ -135,6 +135,49 @@ func (p *DeploymentClient) Down(deploymentRequest *deploy.DeploymentDownRequest) return eventChan, errorChan } +func (p *DeploymentClient) Preview(deploymentRequest *deploy.DeploymentPreviewRequest) (<-chan *deploy.DeploymentPreviewEvent, <-chan error) { + eventChan := make(chan *deploy.DeploymentPreviewEvent) + errorChan := make(chan error) + + go func() { + defer close(eventChan) + + conn, err := p.dialConnection() + if err != nil { + errorChan <- fmt.Errorf("failed to connect to provider at %s: %w", p.address, err) + return + } + defer conn.Close() + + client := deploy.NewDeploymentClient(conn) + + op, err := client.Preview(context.Background(), deploymentRequest) + if err != nil { + errorChan <- err + return + } + + for { + evt, err := op.Recv() + if err != nil { + if !errors.Is(err, io.EOF) { + if st, ok := status.FromError(err); ok { + errorChan <- fmt.Errorf("%s", st.Message()) + } else { + errorChan <- err + } + } + + break + } + + eventChan <- evt + } + }() + + return eventChan, errorChan +} + func NewDeploymentClient(address string, interactive bool) *DeploymentClient { return &DeploymentClient{ address: address, diff --git a/pkg/view/tui/commands/stack/preview/stack_preview.go b/pkg/view/tui/commands/stack/preview/stack_preview.go new file mode 100644 index 000000000..1729ca302 --- /dev/null +++ b/pkg/view/tui/commands/stack/preview/stack_preview.go @@ -0,0 +1,286 @@ +// Copyright Nitric Pty Ltd. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 stack_up + +import ( + "fmt" + "time" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/samber/lo" + + tui "github.com/nitrictech/cli/pkg/view/tui" + "github.com/nitrictech/cli/pkg/view/tui/commands/stack" + "github.com/nitrictech/cli/pkg/view/tui/components/view" + "github.com/nitrictech/cli/pkg/view/tui/fragments" + "github.com/nitrictech/cli/pkg/view/tui/reactive" + "github.com/nitrictech/cli/pkg/view/tui/teax" + deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1" +) + +type Model struct { + windowSize tea.WindowSizeMsg + + provider string + stack *stack.Resource + defaultParent *stack.Resource + updatesChan <-chan *deploymentspb.DeploymentPreviewEvent + errorChan <-chan error + providerStdoutChan <-chan string + providerStdout []string + providerMessages []string + errs []error + resultOutput string + done bool + + spinner spinner.Model +} + +var _ tea.Model = Model{} + +func (m Model) Init() tea.Cmd { + return tea.Batch( + m.spinner.Tick, + reactive.AwaitChannel(m.updatesChan), + reactive.AwaitChannel(m.errorChan), + reactive.AwaitChannel(m.providerStdoutChan), + ) +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.windowSize = msg + + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, tui.KeyMap.Quit): + m.done = true + return m, teax.Quit + } + + case reactive.ChanMsg[string]: + if !msg.Ok { + break + } + + m.providerStdout = append(m.providerStdout, msg.Value) + + return m, reactive.AwaitChannel(msg.Source) + case reactive.ChanMsg[*deploymentspb.DeploymentPreviewEvent]: + // the source channel is closed + if !msg.Ok { + m.done = true + return m, teax.Quit + } + + switch content := msg.Value.Content.(type) { + case *deploymentspb.DeploymentPreviewEvent_Message: + m.providerMessages = append(m.providerMessages, content.Message) + case *deploymentspb.DeploymentPreviewEvent_Update: + if content.Update == nil { + break + } + + name := content.Update.SubResource + if name == "" && content.Update.Id != nil { + name = fmt.Sprintf("%s::%s", content.Update.Id.Type.String(), content.Update.Id.Name) + } + + parent := m.stack + + if content.Update.SubResource != "" && content.Update.Id != nil { + nitricResource, found := lo.Find(m.stack.Children, func(r *stack.Resource) bool { + return r.Name == fmt.Sprintf("%s::%s", content.Update.Id.Type.String(), content.Update.Id.Name) + }) + + if found { + parent = nitricResource + } else { + // add to the default container, used for resources that are stack level, but not explicitly defined. + parent = m.defaultParent + } + } else if content.Update.SubResource != "" { + parent = m.defaultParent + } + + existingChild, found := lo.Find(parent.Children, func(item *stack.Resource) bool { + return item.Name == name + }) + + now := time.Now() + + if !found { + existingChild = &stack.Resource{ + Name: name, + Action: content.Update.Action, + StartTime: now, + } + + parent.Children = append(parent.Children, existingChild) + } + + if content.Update.Status == deploymentspb.ResourceDeploymentStatus_FAILED || content.Update.Status == deploymentspb.ResourceDeploymentStatus_SUCCESS || content.Update.Action == deploymentspb.ResourceDeploymentAction_SAME { + existingChild.FinishTime = now + } + + // update its status + existingChild.Status = content.Update.Status + existingChild.Message = content.Update.Message + case *deploymentspb.DeploymentPreviewEvent_Result: + m.resultOutput = content.Result.GetText() + } + + return m, reactive.AwaitChannel(msg.Source) + case reactive.ChanMsg[error]: + m.errs = append(m.errs, msg.Value) + + return m, nil + case spinner.TickMsg: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +const maxOutputLines = 5 + +var ( + terminalBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, true, false).BorderForeground(tui.Colors.Purple) + errorStyle = lipgloss.NewStyle().Foreground(tui.Colors.Red) +) + +func (m Model) View() string { + margin := fragments.TagWidth() + 2 + if m.windowSize.Width < 60 { + margin = 0 + } + + v := view.New(view.WithStyle(lipgloss.NewStyle().Width(m.windowSize.Width))) + v.Break() + v.Add(fragments.Tag("up")) + v.Addf(" Previewing deployment with %s", m.provider) + + if m.done { + v.Break() + } else { + v.Addln(m.spinner.View()) + } + + v.Break() + + if len(m.providerMessages) > 0 { + for _, message := range m.providerMessages { + v.Addln(message).WithStyle(lipgloss.NewStyle().MarginLeft(margin)) + } + + v.Break() + } + + // Not all providers report a stack tree, so we only render it if there are children + if len(m.stack.Children) > 1 { + statusTree := fragments.NewStatusNode("stack", "") + + for _, child := range m.stack.Children { + currentNode := statusTree.AddNode(child.Name, "") + + for _, grandchild := range child.Children { + resourceTime := lo.Ternary(grandchild.FinishTime.IsZero(), time.Since(grandchild.StartTime).Round(time.Second), grandchild.FinishTime.Sub(grandchild.StartTime)) + + statusColor := tui.Colors.Blue + if grandchild.Status == deploymentspb.ResourceDeploymentStatus_FAILED { + statusColor = tui.Colors.Red + } else if grandchild.Status == deploymentspb.ResourceDeploymentStatus_SUCCESS || grandchild.Action == deploymentspb.ResourceDeploymentAction_SAME { + statusColor = tui.Colors.Green + } + + statusText := fmt.Sprintf("%s (%s)", stack.VerbMap[grandchild.Action][grandchild.Status], resourceTime.Round(time.Second)) + currentNode.AddNode(grandchild.Name, lipgloss.NewStyle().Foreground(statusColor).Render(statusText)) + } + } + + // when the final output is rendered the available output width is 5 characters narrower than the window size. + lastRunFix := 5 + + v.Addln(statusTree.Render(m.windowSize.Width - margin - lastRunFix)).WithStyle(lipgloss.NewStyle().MarginLeft(margin)) + } + + // Provider Stdout and Stderr rendering + if len(m.providerStdout) > 0 { + v.Addln("%s stdout:", m.provider).WithStyle(lipgloss.NewStyle().Bold(true).Foreground(tui.Colors.Blue)) + + providerTerm := view.New(view.WithStyle(terminalBorderStyle)) + + for i, line := range m.providerStdout[max(0, len(m.providerStdout)-maxOutputLines):] { + providerTerm.Add(line).WithStyle(lipgloss.NewStyle().Width(min(m.windowSize.Width, 100))) + + if i < len(m.providerStdout)-1 { + providerTerm.Break() + } + } + + v.Addln(providerTerm.Render()) + } + + for _, e := range m.errs[max(0, len(m.errs)-maxOutputLines):] { + v.Break() + v.Add(fragments.ErrorTag()) + v.Addln(" %s", e.Error()).WithStyle(errorStyle) + } + + if m.resultOutput != "" { + v.Break() + v.Addln(fragments.Tag("result")) + v.Addln("\n%s", m.resultOutput) + } + + return v.Render() +} + +func New(providerName string, stackName string, updatesChan <-chan *deploymentspb.DeploymentPreviewEvent, providerStdoutChan <-chan string, errorChan <-chan error) Model { + orphanParent := &stack.Resource{ + Name: fmt.Sprintf("Stack::%s", stackName), + Message: "", + Action: deploymentspb.ResourceDeploymentAction_SAME, + Status: deploymentspb.ResourceDeploymentStatus_PENDING, + Children: []*stack.Resource{}, + } + + return Model{ + provider: providerName, + spinner: spinner.New(spinner.WithSpinner(spinner.Ellipsis)), + updatesChan: updatesChan, + providerStdoutChan: providerStdoutChan, + errorChan: errorChan, + defaultParent: orphanParent, + stack: &stack.Resource{ + Name: "stack", + Message: "", + Children: []*stack.Resource{ + orphanParent, + }, + }, + } +} From 925aea5e2ef5cc9084ac2d766da8412a967c11fe Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Wed, 29 Jan 2025 11:54:29 +1100 Subject: [PATCH 2/3] add confirmation to deployment after preview --- cmd/stack.go | 145 ++++++++---------- .../stack/preview/confirm_deployment.go | 69 +++++++++ .../commands/stack/preview/stack_preview.go | 16 +- .../tui/components/listprompt/listprompt.go | 4 +- 4 files changed, 147 insertions(+), 87 deletions(-) create mode 100644 pkg/view/tui/commands/stack/preview/confirm_deployment.go diff --git a/cmd/stack.go b/cmd/stack.go index c59e8eb29..3875f5262 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -43,6 +43,7 @@ import ( stack_new "github.com/nitrictech/cli/pkg/view/tui/commands/stack/new" stack_preview "github.com/nitrictech/cli/pkg/view/tui/commands/stack/preview" stack_select "github.com/nitrictech/cli/pkg/view/tui/commands/stack/select" + stack_up "github.com/nitrictech/cli/pkg/view/tui/commands/stack/up" "github.com/nitrictech/cli/pkg/view/tui/components/list" "github.com/nitrictech/cli/pkg/view/tui/components/listprompt" "github.com/nitrictech/cli/pkg/view/tui/components/view" @@ -371,9 +372,7 @@ var stackUpdateCmd = &cobra.Command{ } } - // Check if the thing is active - - confirmModel := listprompt.NewListPrompt(listprompt.ListPromptArgs{ + confirmModel := stack_preview.NewConfirmDeployment(listprompt.ListPromptArgs{ Items: list.StringsToListItems([]string{"confirm deployment", "cancel"}), Tag: "deploy", Prompt: "Finished preview. Would you like to perform the deployment?", @@ -382,84 +381,76 @@ var stackUpdateCmd = &cobra.Command{ selection, err := teax.NewProgram(confirmModel).Run() tui.CheckErr(err) - v := view.New() - v.Break() - fmt.Println("") - - stackSelection = selection.(listprompt.ListPrompt).Choice() + stackSelection = selection.(stack_preview.ConfirmDeploymentModel).Choice() if stackSelection == "cancel" { - v.Addln("Cancelling deployment") - fmt.Println(v.Render()) + fmt.Println("Cancelled") return } - v.Addln("Deployed") - fmt.Println(v.Render()) + eventChan, errorChan := deploymentClient.Up(&deploymentspb.DeploymentUpRequest{ + Spec: spec, + Attributes: attributesStruct, + Interactive: true, + }) + + // Step 5d. Communicate with server to share progress of update + if isNonInteractive() { + providerErrorDetected := false + + fmt.Printf("Deploying %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) + go func() { + for update := range errorChan { + fmt.Printf("Error: %s\n", update) + providerErrorDetected = true + } + }() + + go func() { + for outMessage := range providerStdout { + fmt.Printf("%s: %s\n", stackConfig.Provider, outMessage) + } + }() + + // non-interactive environment + for update := range eventChan { + switch content := update.Content.(type) { + case *deploymentspb.DeploymentUpEvent_Message: + fmt.Printf("%s\n", content.Message) + case *deploymentspb.DeploymentUpEvent_Update: + updateResType := "" + updateResName := "" + if content.Update.Id != nil { + updateResType = content.Update.Id.Type.String() + updateResName = content.Update.Id.Name + } + + if updateResType == "" { + updateResType = "Stack" + } + if updateResName == "" { + updateResName = stackConfig.Name + } + if content.Update.SubResource != "" { + updateResName = fmt.Sprintf("%s:%s", updateResName, content.Update.SubResource) + } + + fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) + case *deploymentspb.DeploymentUpEvent_Result: + fmt.Printf("\nResult: %s\n", content.Result.GetText()) + } + } - // eventChan, errorChan := deploymentClient.Up(&deploymentspb.DeploymentUpRequest{ - // Spec: spec, - // Attributes: attributesStruct, - // Interactive: true, - // }) - - // // Step 5d. Communicate with server to share progress of update - // if isNonInteractive() { - // providerErrorDetected := false - - // fmt.Printf("Deploying %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) - // go func() { - // for update := range errorChan { - // fmt.Printf("Error: %s\n", update) - // providerErrorDetected = true - // } - // }() - - // go func() { - // for outMessage := range providerStdout { - // fmt.Printf("%s: %s\n", stackConfig.Provider, outMessage) - // } - // }() - - // // non-interactive environment - // for update := range eventChan { - // switch content := update.Content.(type) { - // case *deploymentspb.DeploymentUpEvent_Message: - // fmt.Printf("%s\n", content.Message) - // case *deploymentspb.DeploymentUpEvent_Update: - // updateResType := "" - // updateResName := "" - // if content.Update.Id != nil { - // updateResType = content.Update.Id.Type.String() - // updateResName = content.Update.Id.Name - // } - - // if updateResType == "" { - // updateResType = "Stack" - // } - // if updateResName == "" { - // updateResName = stackConfig.Name - // } - // if content.Update.SubResource != "" { - // updateResName = fmt.Sprintf("%s:%s", updateResName, content.Update.SubResource) - // } - - // fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) - // case *deploymentspb.DeploymentUpEvent_Result: - // fmt.Printf("\nResult: %s\n", content.Result.GetText()) - // } - // } - - // // ensure the process exits with a non-zero status code after all messages are processed - // if providerErrorDetected { - // os.Exit(1) - // } - // } else { - // // interactive environment - // // Step 5e. Start the stack up view - // stackUp := stack_up.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) - // _, err = teax.NewProgram(stackUp).Run() - // tui.CheckErr(err) - // } + // ensure the process exits with a non-zero status code after all messages are processed + if providerErrorDetected { + os.Exit(1) + } + } else { + // interactive environment + // Step 5e. Start the stack up view + stackUp := stack_up.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) + _, err = teax.NewProgram(stackUp).Run() + tui.CheckErr(err) + } }, Args: cobra.MinimumNArgs(0), Aliases: []string{"up"}, @@ -990,7 +981,7 @@ func init() { stackUpdateCmd.Flags().BoolVarP(&noBuilder, "no-builder", "", false, "don't create a buildx container") stackUpdateCmd.Flags().StringVarP(&envFile, "env-file", "e", "", "--env-file config/.my-env") stackUpdateCmd.Flags().BoolVarP(&forceStack, "force", "f", false, "force override previous deployment") - stackUpdateCmd.Flags().BoolVarP(&skipPreview, "skip-preview", "", false, "ignore preview") + stackUpdateCmd.Flags().BoolVarP(&skipPreview, "skip-preview", "", false, "skips the preview step of the deployment") tui.CheckErr(AddOptions(stackUpdateCmd, false)) // Delete Stack (Down) diff --git a/pkg/view/tui/commands/stack/preview/confirm_deployment.go b/pkg/view/tui/commands/stack/preview/confirm_deployment.go new file mode 100644 index 000000000..2431d2ab0 --- /dev/null +++ b/pkg/view/tui/commands/stack/preview/confirm_deployment.go @@ -0,0 +1,69 @@ +package stack_preview + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + tui "github.com/nitrictech/cli/pkg/view/tui" + "github.com/nitrictech/cli/pkg/view/tui/components/listprompt" + "github.com/nitrictech/cli/pkg/view/tui/teax" +) + +type ConfirmDeploymentModel struct { + windowSize tea.WindowSizeMsg + + confirmPrompt listprompt.ListPrompt +} + +// Init initializes the model, used by Bubbletea +func (m ConfirmDeploymentModel) Init() tea.Cmd { + return nil +} + +// Update the model based on a message +func (m ConfirmDeploymentModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.windowSize = msg + + if m.windowSize.Height < 7 { + m.confirmPrompt.SetMinimized(true) + m.confirmPrompt.SetMaxDisplayedItems(m.windowSize.Height - 1) + } else { + m.confirmPrompt.SetMinimized(false) + maxItems := ((m.windowSize.Height - 1) / 3) // make room for the exit message + m.confirmPrompt.SetMaxDisplayedItems(maxItems) + } + + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, tui.KeyMap.Quit): + return m, teax.Quit + } + } + + m.confirmPrompt, cmd = m.confirmPrompt.UpdateListPrompt(msg) + if m.confirmPrompt.IsComplete() { + return m, teax.Quit + } + + return m, cmd +} + +func (m ConfirmDeploymentModel) View() string { + return m.confirmPrompt.View() +} + +func (m ConfirmDeploymentModel) Choice() string { + return m.confirmPrompt.Choice() +} + +func NewConfirmDeployment(args listprompt.ListPromptArgs) *ConfirmDeploymentModel { + confirmPrompt := listprompt.NewListPrompt(args) + + return &ConfirmDeploymentModel{ + confirmPrompt: confirmPrompt, + } +} diff --git a/pkg/view/tui/commands/stack/preview/stack_preview.go b/pkg/view/tui/commands/stack/preview/stack_preview.go index 1729ca302..705b4cc8a 100644 --- a/pkg/view/tui/commands/stack/preview/stack_preview.go +++ b/pkg/view/tui/commands/stack/preview/stack_preview.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package stack_up +package stack_preview import ( "fmt" @@ -207,17 +207,19 @@ func (m Model) View() string { currentNode := statusTree.AddNode(child.Name, "") for _, grandchild := range child.Children { - resourceTime := lo.Ternary(grandchild.FinishTime.IsZero(), time.Since(grandchild.StartTime).Round(time.Second), grandchild.FinishTime.Sub(grandchild.StartTime)) - statusColor := tui.Colors.Blue - if grandchild.Status == deploymentspb.ResourceDeploymentStatus_FAILED { + if grandchild.Action == deploymentspb.ResourceDeploymentAction_DELETE { statusColor = tui.Colors.Red - } else if grandchild.Status == deploymentspb.ResourceDeploymentStatus_SUCCESS || grandchild.Action == deploymentspb.ResourceDeploymentAction_SAME { + } else if grandchild.Action == deploymentspb.ResourceDeploymentAction_CREATE { statusColor = tui.Colors.Green + } else if grandchild.Action == deploymentspb.ResourceDeploymentAction_SAME { + statusColor = tui.Colors.Gray } - statusText := fmt.Sprintf("%s (%s)", stack.VerbMap[grandchild.Action][grandchild.Status], resourceTime.Round(time.Second)) - currentNode.AddNode(grandchild.Name, lipgloss.NewStyle().Foreground(statusColor).Render(statusText)) + // Always uses the pending verbage to show it will happen, not that it has happened + statusVerbage := stack.VerbMap[grandchild.Action][deploymentspb.ResourceDeploymentStatus_PENDING] + + currentNode.AddNode(grandchild.Name, lipgloss.NewStyle().Foreground(statusColor).Render(statusVerbage)) } } diff --git a/pkg/view/tui/components/listprompt/listprompt.go b/pkg/view/tui/components/listprompt/listprompt.go index b4eac5272..fcdf5e8cf 100644 --- a/pkg/view/tui/components/listprompt/listprompt.go +++ b/pkg/view/tui/components/listprompt/listprompt.go @@ -17,8 +17,6 @@ package listprompt import ( - "strings" - "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -105,7 +103,7 @@ func (m ListPrompt) View() string { listView.Addln(m.Choice()).WithStyle(historyTextStyle) } - return strings.TrimSuffix(listView.Render(), "\n") + return listView.Render() } type ListPromptArgs struct { From 1e10b3fa98b1aa062d5e4bbc647ad261887475ec Mon Sep 17 00:00:00 2001 From: Ryan Cartwright Date: Wed, 29 Jan 2025 12:16:01 +1100 Subject: [PATCH 3/3] stop preview on ci flag --- cmd/stack.go | 83 ++++--------------- .../commands/stack/preview/stack_preview.go | 6 +- 2 files changed, 17 insertions(+), 72 deletions(-) diff --git a/cmd/stack.go b/cmd/stack.go index 3875f5262..a151edbe7 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -306,87 +306,32 @@ var stackUpdateCmd = &cobra.Command{ attributesStruct, err := structpb.NewStruct(attributes) tui.CheckErr(err) - if !skipPreview { + if !skipPreview && !isNonInteractive() { eventChan, errorChan := deploymentClient.Preview(&deploymentspb.DeploymentPreviewRequest{ Spec: spec, Attributes: attributesStruct, Interactive: true, }) - // Step 5b. Communicate with server to get preview - if isNonInteractive() { - providerErrorDetected := false - - fmt.Printf("Previewing %s stack with provider %s\n", stackConfig.Name, stackConfig.Provider) - go func() { - for update := range errorChan { - fmt.Printf("Error: %s\n", update) - providerErrorDetected = true - } - }() - - go func() { - for outMessage := range providerStdout { - fmt.Printf("%s: %s\n", stackConfig.Provider, outMessage) - } - }() - - // non-interactive environment - for update := range eventChan { - switch content := update.Content.(type) { - case *deploymentspb.DeploymentPreviewEvent_Message: - fmt.Printf("%s\n", content.Message) - case *deploymentspb.DeploymentPreviewEvent_Update: - updateResType := "" - updateResName := "" - if content.Update.Id != nil { - updateResType = content.Update.Id.Type.String() - updateResName = content.Update.Id.Name - } - - if updateResType == "" { - updateResType = "Stack" - } - if updateResName == "" { - updateResName = stackConfig.Name - } - if content.Update.SubResource != "" { - updateResName = fmt.Sprintf("%s:%s", updateResName, content.Update.SubResource) - } + stackPreview := stack_preview.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) + _, err = teax.NewProgram(stackPreview).Run() - fmt.Printf("%s:%s [%s]:%s %s\n", updateResType, updateResName, content.Update.Action, content.Update.Status, content.Update.Message) - case *deploymentspb.DeploymentPreviewEvent_Result: - fmt.Printf("\nResult: %s\n", content.Result.GetText()) - } - } + confirmModel := stack_preview.NewConfirmDeployment(listprompt.ListPromptArgs{ + Items: list.StringsToListItems([]string{"confirm deployment", "cancel"}), + Tag: "deploy", + Prompt: "Would you like to perform the deployment?", + }) - // ensure the process exits with a non-zero status code after all messages are processed - if providerErrorDetected { + selection, err := teax.NewProgram(confirmModel).Run() + tui.CheckErr(err) - } - } else { - // interactive environment - // Step 5c. Start the stack preview view\ - stackPreview := stack_preview.New(stackConfig.Provider, stackConfig.Name, eventChan, providerStdout, errorChan) - _, err = teax.NewProgram(stackPreview).Run() + stackSelection = selection.(stack_preview.ConfirmDeploymentModel).Choice() + if stackSelection == "cancel" { + fmt.Println("Cancelled") + return } } - confirmModel := stack_preview.NewConfirmDeployment(listprompt.ListPromptArgs{ - Items: list.StringsToListItems([]string{"confirm deployment", "cancel"}), - Tag: "deploy", - Prompt: "Finished preview. Would you like to perform the deployment?", - }) - - selection, err := teax.NewProgram(confirmModel).Run() - tui.CheckErr(err) - - stackSelection = selection.(stack_preview.ConfirmDeploymentModel).Choice() - if stackSelection == "cancel" { - fmt.Println("Cancelled") - return - } - eventChan, errorChan := deploymentClient.Up(&deploymentspb.DeploymentUpRequest{ Spec: spec, Attributes: attributesStruct, diff --git a/pkg/view/tui/commands/stack/preview/stack_preview.go b/pkg/view/tui/commands/stack/preview/stack_preview.go index 705b4cc8a..eb623fe83 100644 --- a/pkg/view/tui/commands/stack/preview/stack_preview.go +++ b/pkg/view/tui/commands/stack/preview/stack_preview.go @@ -180,7 +180,7 @@ func (m Model) View() string { v := view.New(view.WithStyle(lipgloss.NewStyle().Width(m.windowSize.Width))) v.Break() - v.Add(fragments.Tag("up")) + v.Add(fragments.Tag("preview")) v.Addf(" Previewing deployment with %s", m.provider) if m.done { @@ -254,8 +254,8 @@ func (m Model) View() string { if m.resultOutput != "" { v.Break() - v.Addln(fragments.Tag("result")) - v.Addln("\n%s", m.resultOutput) + v.Add(fragments.Tag("plan")) + v.Addln(" %s", m.resultOutput) } return v.Render()