diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 2e89cff56d..4c31a6d1b5 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -11,6 +11,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/ssh" + "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/system" "github.com/sirupsen/logrus" @@ -54,6 +55,7 @@ var ( Port int UDSPath string Default bool + Farm string }{} ) @@ -76,6 +78,11 @@ func init() { flags.StringVar(&cOpts.UDSPath, socketPathFlagName, "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") _ = addCmd.RegisterFlagCompletionFunc(socketPathFlagName, completion.AutocompleteDefault) + farmFlagName := "farm" + flags.StringVarP(&cOpts.Farm, farmFlagName, "f", "", "Add the new connection to the given farm") + _ = addCmd.RegisterFlagCompletionFunc(farmFlagName, common.AutoCompleteFarms) + _ = flags.MarkHidden(farmFlagName) + flags.BoolVarP(&cOpts.Default, "default", "d", false, "Set connection to be default") registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -104,6 +111,7 @@ func add(cmd *cobra.Command, args []string) error { Name: args[0], Socket: cOpts.UDSPath, Default: cOpts.Default, + Farm: cOpts.Farm, } dest := args[1] if match, err := regexp.MatchString("^[A-Za-z][A-Za-z0-9+.-]*://", dest); err != nil { @@ -195,6 +203,21 @@ func add(cmd *cobra.Command, args []string) error { } else { cfg.Engine.ServiceDestinations[args[0]] = dst } + + if cOpts.Farm != "" { + if cfg.Farms.List == nil { + cfg.Farms.List = map[string][]string{ + cOpts.Farm: {args[0]}, + } + cfg.Farms.Default = cOpts.Farm + } else { + if val, ok := cfg.Farms.List[cOpts.Farm]; ok { + cfg.Farms.List[cOpts.Farm] = append(val, args[0]) + } else { + cfg.Farms.List[cOpts.Farm] = []string{args[0]} + } + } + } return cfg.Write() } diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go index 5ff0000d61..dbe4192fa7 100644 --- a/cmd/podman/system/connection/remove.go +++ b/cmd/podman/system/connection/remove.go @@ -7,6 +7,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/system" + "github.com/containers/podman/v4/pkg/util" "github.com/spf13/cobra" ) @@ -59,6 +60,13 @@ func rm(cmd *cobra.Command, args []string) error { } } cfg.Engine.ActiveService = "" + + // Clear all the connections in any existing farms + if cfg.Farms.List != nil { + for k := range cfg.Farms.List { + cfg.Farms.List[k] = []string{} + } + } return cfg.Write() } @@ -74,5 +82,15 @@ func rm(cmd *cobra.Command, args []string) error { cfg.Engine.ActiveService = "" } + // If there are existing farm, remove the deleted connection that might be part of a farm + if cfg.Farms.List != nil { + for k, v := range cfg.Farms.List { + index := util.IndexOfStringInSlice(args[0], v) + if index > -1 { + cfg.Farms.List[k] = append(v[:index], v[index+1:]...) + } + } + } + return cfg.Write() } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 337f68b3a0..ac7553e95f 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -167,6 +167,17 @@ func StringMatchRegexSlice(s string, re []string) bool { return false } +// IndexOfStringInSlice returns the index if a string is in a slice, otherwise +// it returns -1 if the string is not found +func IndexOfStringInSlice(s string, sl []string) int { + for i := range sl { + if sl[i] == s { + return i + } + } + return -1 +} + // ParseSignal parses and validates a signal name or number. func ParseSignal(rawSignal string) (syscall.Signal, error) { // Strip off leading dash, to allow -1 or -HUP diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go index 8100e3598e..5ab2c31e28 100644 --- a/test/e2e/system_connection_test.go +++ b/test/e2e/system_connection_test.go @@ -113,6 +113,109 @@ var _ = Describe("podman system connection", func() { )) }) + It("add to new farm", func() { + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Farms.List).Should(BeEmpty()) + + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "--farm", "farm1", + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg).Should(HaveActiveService("QA")) + Expect(cfg).Should(VerifyService( + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + "~/.ssh/id_rsa", + )) + Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA"})) + }) + + It("add to existing farm", func() { + // create empty farm + cmd := []string{"farm", "create", "empty-farm"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"empty-farm\" created")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Farms.List).Should(HaveKeyWithValue("empty-farm", []string{})) + + cmd = []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "--farm", "empty-farm", + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + } + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg).Should(HaveActiveService("QA")) + Expect(cfg).Should(VerifyService( + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + "~/.ssh/id_rsa", + )) + Expect(cfg.Farms.List).Should(HaveKeyWithValue("empty-farm", []string{"QA"})) + }) + + It("removing connection should remove from farm also", func() { + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Farms.List).Should(BeEmpty()) + + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "--farm", "farm1", + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg).Should(HaveActiveService("QA")) + Expect(cfg).Should(VerifyService( + "QA", + "ssh://root@podman.test:2222/run/podman/podman.sock", + "~/.ssh/id_rsa", + )) + Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA"})) + + // Remove the QA connection + session = podmanTest.Podman([]string{"system", "connection", "remove", "QA"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out.Contents()).Should(BeEmpty()) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).Should(BeEmpty()) + Expect(cfg.Engine.ServiceDestinations).Should(BeEmpty()) + Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{})) + }) + It("remove", func() { session := podmanTest.Podman([]string{"system", "connection", "add", "--default",