diff --git a/cli/cmd/network.go b/cli/cmd/network.go index 5b694a852..7b1ff247b 100644 --- a/cli/cmd/network.go +++ b/cli/cmd/network.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/pkg/errors" "github.com/replicatedhq/replicated/pkg/credentials" "github.com/replicatedhq/replicated/pkg/kotsclient" @@ -11,10 +12,14 @@ import ( func (r *runners) InitNetworkCommand(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ - Use: "network", - Short: "Manage test networks for VMs and Clusters", - Long: ``, - Hidden: true, + Use: "network", + Short: "Manage test networks for VMs and clusters.", + Long: `Manage test networks for VMs and clusters. + +Networks are automatically created when you run 'replicated vm create' or +'replicated cluster create'. To connect a VM or cluster to an existing network, +use the '--network' flag during creation. Networks are automatically deleted +when all associated VMs and clusters are removed.`, } parent.AddCommand(cmd) diff --git a/cli/cmd/network_create.go b/cli/cmd/network_create.go deleted file mode 100644 index 9cbdc139d..000000000 --- a/cli/cmd/network_create.go +++ /dev/null @@ -1,111 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "time" - - "github.com/moby/moby/pkg/namesgenerator" - "github.com/pkg/errors" - "github.com/replicatedhq/replicated/cli/print" - "github.com/replicatedhq/replicated/pkg/kotsclient" - "github.com/replicatedhq/replicated/pkg/platformclient" - "github.com/replicatedhq/replicated/pkg/types" - "github.com/spf13/cobra" -) - -func (r *runners) InitNetworkCreate(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Short: "Create networks for VMs and Clusters", - Long: ``, - SilenceUsage: true, - RunE: r.createNetwork, - } - parent.AddCommand(cmd) - - cmd.Flags().StringVar(&r.args.createNetworkName, "name", "", "Network name (defaults to random name)") - cmd.Flags().StringVar(&r.args.createNetworkTTL, "ttl", "", "Network TTL (duration, max 48h)") - cmd.Flags().DurationVar(&r.args.createNetworkWaitDuration, "wait", time.Second*0, "Wait duration for Network to be ready (leave empty to not wait)") - - cmd.Flags().BoolVar(&r.args.createNetworkDryRun, "dry-run", false, "Dry run") - - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - - return cmd -} - -func (r *runners) createNetwork(_ *cobra.Command, args []string) error { - if r.args.createNetworkName == "" { - r.args.createNetworkName = namesgenerator.GetRandomName(0) - } - - opts := kotsclient.CreateNetworkOpts{ - Name: r.args.createNetworkName, - TTL: r.args.createNetworkTTL, - DryRun: r.args.createNetworkDryRun, - } - - network, err := r.createAndWaitForNetwork(opts) - if err != nil { - if errors.Cause(err) == ErrVMWaitDurationExceeded { - defer os.Exit(124) - } else { - return err - } - } - - if opts.DryRun { - _, err = fmt.Fprintln(r.w, "Dry run succeeded.") - return err - } - - return print.Network(r.outputFormat, r.w, network) -} - -func (r *runners) createAndWaitForNetwork(opts kotsclient.CreateNetworkOpts) (*types.Network, error) { - network, ve, err := r.kotsAPI.CreateNetwork(opts) - if errors.Cause(err) == platformclient.ErrForbidden { - return nil, ErrCompatibilityMatrixTermsNotAccepted - } else if err != nil { - return nil, errors.Wrap(err, "create network") - } - - if ve != nil && ve.Message != "" { - return nil, errors.New(ve.Message) - } - - if opts.DryRun { - return network, nil - } - - // if the wait flag was provided, we poll the api until the network is ready, or a timeout - if r.args.createNetworkWaitDuration > 0 { - return waitForNetwork(r.kotsAPI, network, r.args.createNetworkWaitDuration) - } - - return network, nil -} - -func waitForNetwork(kotsRestClient *kotsclient.VendorV3Client, network *types.Network, duration time.Duration) (*types.Network, error) { - start := time.Now() - for { - network, err := kotsRestClient.GetNetwork(network.ID) - if err != nil { - return nil, errors.Wrap(err, "get network") - } - - if network.Status == types.NetworkStatusRunning { - return network, nil - } - if network.Status == types.NetworkStatusError { - return nil, errors.New("network failed to provision") - } - if time.Now().After(start.Add(duration)) { - // In case of timeout, return the network and a WaitDurationExceeded error - return network, ErrWaitDurationExceeded - } - - time.Sleep(time.Second * 5) - } -} diff --git a/cli/cmd/network_join.go b/cli/cmd/network_join.go deleted file mode 100644 index 9aaf01286..000000000 --- a/cli/cmd/network_join.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -func (r *runners) InitNetworkJoin(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "join", - Short: "Join a test network", - Long: ``, - RunE: r.joinNetwork, - } - parent.AddCommand(cmd) - - return cmd -} - -func (r *runners) joinNetwork(_ *cobra.Command, args []string) error { - return nil -} diff --git a/cli/cmd/network_ls.go b/cli/cmd/network_ls.go index 4ab3022ad..7ab87f3f4 100644 --- a/cli/cmd/network_ls.go +++ b/cli/cmd/network_ls.go @@ -16,8 +16,8 @@ func (r *runners) InitNetworkList(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "ls", Aliases: []string{"list"}, - Short: "List test networks", - Long: ``, + Short: "List test networks.", + Long: `List created test networks for VMs and clusters.`, RunE: r.listNetworks, } parent.AddCommand(cmd) diff --git a/cli/cmd/network_rm.go b/cli/cmd/network_rm.go deleted file mode 100644 index 2969ddade..000000000 --- a/cli/cmd/network_rm.go +++ /dev/null @@ -1,139 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/replicatedhq/replicated/pkg/platformclient" - "github.com/spf13/cobra" -) - -func (r *runners) InitNetworkRemove(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "rm ID_OR_NAME [ID_OR_NAME …]", - Aliases: []string{"delete"}, - Short: "Remove test network(s) immediately, with options to filter by name or remove all networks.", - Long: `The 'rm' command allows you to remove test networks from your account immediately. You can specify one or more network IDs or names directly, or use flags to filter which networks to remove based on their name or simply remove all networks at once. - -This command supports multiple filtering options, including removing networks by their name or by specifying the '--all' flag to remove all networks in your account. - -When specifying a name that matches multiple networks, all networks with that name will be removed. - -You can also use the '--dry-run' flag to simulate the removal without actually deleting the networks.`, - Example: `# Remove a network by ID or name -replicated network rm aaaaa11 - -# Remove multiple networks by ID or name -replicated network rm aaaaa11 bbbbb22 ccccc33 - -# Remove all networks with a specific name -replicated network rm --name test-network - -# Remove all networks -replicated network rm --all - -# Perform a dry run of removing all networks -replicated network rm --all --dry-run`, - RunE: r.removeNetworks, - ValidArgsFunction: r.completeNetworkIDsAndNames, - } - parent.AddCommand(cmd) - - cmd.Flags().StringArrayVar(&r.args.removeNetworkNames, "name", []string{}, "Name of the network to remove (can be specified multiple times)") - cmd.RegisterFlagCompletionFunc("name", r.completeNetworkNames) - cmd.Flag("name").Deprecated = "use ID_OR_NAME arguments instead" - - cmd.Flags().BoolVar(&r.args.removeNetworkAll, "all", false, "remove all networks") - cmd.Flags().BoolVar(&r.args.removeNetworkDryRun, "dry-run", false, "Dry run") - - return cmd -} - -func (r *runners) removeNetworks(_ *cobra.Command, args []string) error { - if len(args) == 0 && !r.args.removeNetworkAll && len(r.args.removeNetworkNames) == 0 { - return errors.New("One of ID, --all, or --name flag required") - } else if len(args) > 0 && (r.args.removeNetworkAll || len(r.args.removeNetworkNames) > 0) { - return errors.New("cannot specify ID and --all or --name flag") - } else if len(args) == 0 && r.args.removeNetworkAll && len(r.args.removeNetworkNames) > 0 { - return errors.New("cannot specify --all and --name flag") - } - - if len(r.args.removeNetworkNames) > 0 { - networks, err := r.kotsAPI.ListNetworks(nil, nil) - if err != nil { - return errors.Wrap(err, "list networks") - } - for _, network := range networks { - for _, name := range r.args.removeNetworkNames { - if network.Name == name { - err := removeNetwork(r, network.ID) - if err != nil { - return errors.Wrap(err, "remove network") - } - } - } - } - } - - if r.args.removeNetworkAll { - networks, err := r.kotsAPI.ListNetworks(nil, nil) - if err != nil { - return errors.Wrap(err, "list networks") - } - for _, network := range networks { - err := removeNetwork(r, network.ID) - if err != nil { - return errors.Wrap(err, "remove network") - } - } - } - - for _, arg := range args { - _, err := r.kotsAPI.GetNetwork(arg) - if err == nil { - err := removeNetwork(r, arg) - if err != nil { - return errors.Wrap(err, "remove network") - } - continue - } - - networks, err := r.kotsAPI.ListNetworks(nil, nil) - if err != nil { - return errors.Wrap(err, "list networks") - } - - found := false - for _, network := range networks { - if network.Name == arg { - found = true - err := removeNetwork(r, network.ID) - if err != nil { - return errors.Wrap(err, "remove network") - } - } - } - - if !found { - return errors.Errorf("Network with name or ID '%s' not found", arg) - } - } - - return nil -} - -func removeNetwork(r *runners, networkID string) error { - if r.args.removeNetworkDryRun { - fmt.Printf("would remove network %s\n", networkID) - return nil - } - err := r.kotsAPI.RemoveNetwork(networkID) - if errors.Cause(err) == platformclient.ErrForbidden { - return ErrCompatibilityMatrixTermsNotAccepted - } else if err != nil { - return errors.Wrap(err, "remove network") - } else { - fmt.Printf("removed network %s\n", networkID) - } - return nil -} diff --git a/cli/cmd/network_update_outbound.go b/cli/cmd/network_update_outbound.go deleted file mode 100644 index 54f880a23..000000000 --- a/cli/cmd/network_update_outbound.go +++ /dev/null @@ -1,71 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/replicatedhq/replicated/cli/print" - "github.com/replicatedhq/replicated/pkg/kotsclient" - "github.com/spf13/cobra" -) - -const ( - noneTranslatedPolicy = "airgap" - anyTranslatedPolicy = "open" -) - -func (r *runners) InitNetworkUpdateOutbound(parent *cobra.Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "outbound [ID_OR_NAME]", - Short: "Update outbound setting for a test network.", - Long: `The 'outbound' command allows you to update the outbound setting of a test network. The outbound setting can be either 'none' or 'any'.`, - Example: `# Update the outbound setting for a specific network -replicated network update outbound NETWORK_ID_OR_NAME --outbound any`, - RunE: r.updateNetworkOutbound, - SilenceUsage: true, - Hidden: true, - ValidArgsFunction: r.completeNetworkIDsAndNames, - } - parent.AddCommand(cmd) - - cmd.Flags().StringVar(&r.args.updateNetworkOutbound, "outbound", "", "Update outbound setting (must be 'none' or 'any')") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - - cmd.MarkFlagRequired("outbound") - - return cmd -} - -func (r *runners) updateNetworkOutbound(cmd *cobra.Command, args []string) error { - fmt.Fprintln(os.Stderr, "Note: 'replicated network update outbound' is deprecated. Use 'replicated network update policy' instead.") - - if err := r.ensureUpdateNetworkIDArg(args); err != nil { - return errors.Wrap(err, "ensure network id arg") - } - - if r.args.updateNetworkOutbound != "none" && r.args.updateNetworkOutbound != "any" { - return errors.New("outbound must be either 'none' or 'any'") - } - - var updateOpts kotsclient.UpdateNetworkPolicyOpts - if r.args.updateNetworkOutbound == "none" { - fmt.Fprintln(os.Stderr, "Updating policy to 'airgap' to match 'none' outbound setting") - updateOpts = kotsclient.UpdateNetworkPolicyOpts{ - Policy: noneTranslatedPolicy, - } - } - if r.args.updateNetworkOutbound == "any" { - fmt.Fprintln(os.Stderr, "Updating policy to 'open' to match 'any' outbound setting") - updateOpts = kotsclient.UpdateNetworkPolicyOpts{ - Policy: anyTranslatedPolicy, - } - } - - network, err := r.kotsAPI.UpdateNetworkPolicy(r.args.updateNetworkID, updateOpts) - if err != nil { - return errors.Wrap(err, "update network policy") - } - - return print.Network(r.outputFormat, r.w, network) -} diff --git a/cli/cmd/root.go b/cli/cmd/root.go index ef074c7c6..efedbd139 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -271,7 +271,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.InitVMRemove(vmCmd) runCmds.InitVMSSHEndpoint(vmCmd) runCmds.InitVMSCPEndpoint(vmCmd) - + vmUpdateCmd := runCmds.InitVMUpdateCommand(vmCmd) runCmds.InitVMUpdateTTL(vmUpdateCmd) @@ -281,13 +281,9 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.InitVMPortRm(vmPortCmd) networkCmd := runCmds.InitNetworkCommand(runCmds.rootCmd) - runCmds.InitNetworkCreate(networkCmd) runCmds.InitNetworkList(networkCmd) - runCmds.InitNetworkRemove(networkCmd) - runCmds.InitNetworkJoin(networkCmd) networkUpdateCmd := runCmds.InitNetworkUpdateCommand(networkCmd) - runCmds.InitNetworkUpdateOutbound(networkUpdateCmd) runCmds.InitNetworkUpdatePolicy(networkUpdateCmd) runCmds.InitLoginCommand(runCmds.rootCmd) diff --git a/cli/print/networks.go b/cli/print/networks.go index c44684226..e0e6a9ee7 100644 --- a/cli/print/networks.go +++ b/cli/print/networks.go @@ -10,22 +10,32 @@ import ( ) // Table formatting -var networksTmplTableHeaderSrc = `ID NAME STATUS CREATED EXPIRES POLICY` -var networksTmplTableRowSrc = `{{ range . -}} -{{ .ID }} {{ padding .Name 27 }} {{ padding (printf "%s" .Status) 12 }} {{ padding (printf "%s" (localeTime .CreatedAt)) 30 }} {{if .ExpiresAt.IsZero}}{{ padding "-" 30 }}{{else}}{{ padding (printf "%s" (localeTime .ExpiresAt)) 30 }}{{end}} {{if eq .Policy ""}}{{ padding "open" 30 }}{{else}}{{ padding (printf "%s" (.Policy)) 30 }}{{end}} +var ( + networksTmplTableHeaderSrc = `{{ padding "ID" 12 }}{{ padding "NAME" 30 }}{{ padding "STATUS" 12 }}{{ padding "CREATED" 25 }}POLICY` + networksTmplTableRowSrc = `{{ range . -}} +{{ padding .ID 12 }}{{ padding .Name 30 }}{{ padding (printf "%s" .Status) 12 }}{{ padding (printf "%s" (localeTime .CreatedAt)) 25 }}{{if eq .Policy ""}}{{ padding "open" 15 }}{{else}}{{ padding (printf "%s" (.Policy)) 15 }}{{end}} {{ end }}` -var networksTmplTableSrc = fmt.Sprintln(networksTmplTableHeaderSrc) + networksTmplTableRowSrc -var networksTmplTable = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplTableSrc)) -var networksTmplTableNoHeader = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplTableRowSrc)) +) + +var ( + networksTmplTableSrc = networksTmplTableHeaderSrc + "\n" + networksTmplTableRowSrc + networksTmplTable = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplTableSrc)) + networksTmplTableNoHeader = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplTableRowSrc)) +) // Wide table formatting -var networksTmplWideHeaderSrc = `ID NAME STATUS CREATED EXPIRES POLICY` -var networksTmplWideRowSrc = `{{ range . -}} -{{ .ID }} {{ padding .Name 27 }} {{ padding (printf "%s" .Status) 12 }} {{ padding (printf "%s" (localeTime .CreatedAt)) 30 }} {{if .ExpiresAt.IsZero}}{{ padding "-" 30 }}{{else}}{{ padding (printf "%s" (localeTime .ExpiresAt)) 30 }}{{end}} {{if eq .Policy ""}}{{ padding "open" 30 }}{{else}}{{ padding (printf "%s" (.Policy)) 30 }}{{end}} +var ( + networksTmplWideHeaderSrc = `{{ padding "ID" 12 }}{{ padding "NAME" 30 }}{{ padding "STATUS" 12 }}{{ padding "CREATED" 25 }}POLICY` + networksTmplWideRowSrc = `{{ range . -}} +{{ padding .ID 12 }}{{ padding .Name 30 }}{{ padding (printf "%s" .Status) 12 }}{{ padding (printf "%s" (localeTime .CreatedAt)) 25 }}{{if eq .Policy ""}}{{ padding "open" 15 }}{{else}}{{ padding (printf "%s" (.Policy)) 15 }}{{end}} {{ end }}` -var networksTmplWideSrc = fmt.Sprintln(networksTmplWideHeaderSrc) + networksTmplWideRowSrc -var networksTmplWide = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplWideSrc)) -var networksTmplWideNoHeader = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplWideRowSrc)) +) + +var ( + networksTmplWideSrc = networksTmplWideHeaderSrc + "\n" + networksTmplWideRowSrc + networksTmplWide = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplWideSrc)) + networksTmplWideNoHeader = template.Must(template.New("networks").Funcs(funcs).Parse(networksTmplWideRowSrc)) +) func Networks(outputFormat string, w *tabwriter.Writer, networks []*types.Network, header bool) error { switch outputFormat { @@ -90,7 +100,7 @@ func Network(outputFormat string, w *tabwriter.Writer, network *types.Network) e func NoNetworks(outputFormat string, w *tabwriter.Writer) error { switch outputFormat { case "table", "wide": - _, err := fmt.Fprintln(w, "No networks found. Use the `replicated network create` command to create a new network.") + _, err := fmt.Fprintln(w, "No networks found. Networks are created alongside VMs or clusters.") if err != nil { return err }