diff --git a/cmd/service.go b/cmd/service.go index ecf1cd0..8f0766b 100644 --- a/cmd/service.go +++ b/cmd/service.go @@ -4,6 +4,7 @@ package cmd import ( "fmt" "os" + "strings" _ "embed" @@ -61,6 +62,10 @@ func (s serviceMode) All() []serviceMode { ServiceModePodman, ServiceModeDocker} } +func (s serviceMode) String() string { + return string(s) +} + func (o *serviceOption) preRunE(c *cobra.Command, args []string) (err error) { if o.action == "" && len(args) > 0 { o.action = args[0] @@ -99,6 +104,7 @@ func (o *serviceOption) runE(c *cobra.Command, args []string) (err error) { err = fmt.Errorf("not support action: '%s'", o.action) } + output = strings.TrimSpace(output) if output != "" { c.Println(output) } @@ -124,12 +130,12 @@ func (a Action) All() []Action { // Service is the interface of service type Service interface { - Start() (string, error) // start the service - Stop() (string, error) // stop the service gracefully - Restart() (string, error) // restart the service gracefully - Status() (string, error) // status of the service - Install() (string, error) // install the service - Uninstall() (string, error) + Start() (string, error) // start the service + Stop() (string, error) // stop the service gracefully + Restart() (string, error) // restart the service gracefully + Status() (string, error) // status of the service + Install() (string, error) // install the service + Uninstall() (string, error) // uninstall the service } func emptyThenDefault(value, defaultValue string) string { @@ -176,16 +182,20 @@ func (o *serviceOption) getContainerService() (service Service, err error) { var client string switch serviceMode(o.mode) { case ServiceModeDocker: - client = "docker" + client = ServiceModeDocker.String() case ServiceModePodman, ServiceModeContainer: - client = "podman" + client = ServiceModePodman.String() default: err = fmt.Errorf("not support mode: '%s'", o.mode) return } - if client, err = o.LookPath(client); err == nil { - service = newContainerService(o.Execer, client, o.image, o.version) + var clientPath string + if clientPath, err = o.LookPath(client); err == nil { + if clientPath == "" { + clientPath = client + } + service = newContainerService(o.Execer, clientPath, o.image, o.version) } return } @@ -247,35 +257,38 @@ type linuxService struct { commonService } +const systemCtl = "systemctl" +const serviceName = "atest" + func (s *linuxService) Start() (output string, err error) { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "start", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "start", serviceName) return } func (s *linuxService) Stop() (output string, err error) { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "stop", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "stop", serviceName) return } func (s *linuxService) Restart() (output string, err error) { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "restart", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "restart", serviceName) return } func (s *linuxService) Status() (output string, err error) { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "status", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "status", serviceName) return } func (s *linuxService) Install() (output string, err error) { if err = os.WriteFile(s.scriptPath, []byte(s.script), os.ModeAppend); err == nil { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "enable", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "enable", serviceName) } return } func (s *linuxService) Uninstall() (output string, err error) { - output, err = s.Execer.RunCommandAndReturn("systemctl", "", "disable", "atest") + output, err = s.Execer.RunCommandAndReturn(systemCtl, "", "disable", serviceName) return } @@ -296,20 +309,31 @@ func newContainerService(execer fakeruntime.Execer, client, image, tag string) ( if image == "" { image = defaultImage } - service = &containerService{ + + containerServer := &containerService{ Execer: execer, client: client, - name: "atest", + name: serviceName, image: image, tag: tag, } + + if strings.HasSuffix(client, ServiceModePodman.String()) { + service = &podmanService{ + containerService: *containerServer, + } + } else { + service = containerServer + } return } func (s *containerService) Start() (output string, err error) { - err = s.Execer.SystemCall(s.client, []string{s.client, "run", "--name=" + s.name, - "--restart=always", "-d", "--pull=always", "--network=host", - s.image + ":" + s.tag}, os.Environ()) + if s.exist() { + output, err = s.Execer.RunCommandAndReturn(s.client, "", "start", s.name) + } else { + err = s.Execer.SystemCall(s.client, append([]string{s.client}, s.getStartArgs()...), os.Environ()) + } return } @@ -323,7 +347,8 @@ func (s *containerService) Restart() (output string, err error) { return } -func (s *containerService) Status() (output string, err error) { +func (s *containerService) Status() (_ string, err error) { + err = s.Execer.SystemCall(s.client, []string{s.client, "stats", s.name}, os.Environ()) return } @@ -336,3 +361,33 @@ func (s *containerService) Uninstall() (output string, err error) { output, err = s.Stop() return } + +func (s *containerService) exist() bool { + output, err := s.Execer.RunCommandAndReturn(s.client, "", "ps", "--all", "--filter", fmt.Sprintf("name=%s", s.name)) + return err == nil && strings.Contains(output, s.name) +} + +func (s *containerService) getStartArgs() []string { + return []string{"run", "--name=" + s.name, + "--restart=always", "-d", "--pull=always", "--network=host", + s.image + ":" + s.tag} +} + +type podmanService struct { + containerService +} + +func (s *podmanService) Start() (output string, err error) { + if s.exist() { + output, err = s.Execer.RunCommandAndReturn(s.client, "", "start", s.name) + } else { + output, err = s.Execer.RunCommandAndReturn(s.client, "", s.getStartArgs()...) + + if err == nil { + var result string + result, err = s.Execer.RunCommandAndReturn(s.client, "", "generate", "systemd", "--new", "--files", "--name", s.name) + output = fmt.Sprintf("%s\n%s", output, result) + } + } + return +} diff --git a/cmd/service_test.go b/cmd/service_test.go index ca42de8..17757af 100644 --- a/cmd/service_test.go +++ b/cmd/service_test.go @@ -143,6 +143,12 @@ func TestService(t *testing.T) { targetOS: fakeruntime.OSLinux, mode: string(ServiceModeDocker), expectOutput: "", + }, { + name: "start in podman", + action: "start", + targetOS: fakeruntime.OSLinux, + mode: ServiceModePodman.String(), + expectOutput: "", }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/console/atest-ui/package-lock.json b/console/atest-ui/package-lock.json index 51c548f..d7be43b 100644 --- a/console/atest-ui/package-lock.json +++ b/console/atest-ui/package-lock.json @@ -36,8 +36,6 @@ "grpc_tools_node_protoc_ts": "^5.3.3", "grpc-tools": "^1.12.4", "jest": "^29.6.1", - "jest-fetch-mock": "^3.0.3", - "jest-mock-fetch": "^2.0.5", "jsdom": "^22.1.0", "npm-run-all": "^4.1.5", "prettier": "^2.8.8", @@ -4200,15 +4198,6 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7459,16 +7448,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-fetch-mock": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", - "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", - "dev": true, - "dependencies": { - "cross-fetch": "^3.0.4", - "promise-polyfill": "^8.1.3" - } - }, "node_modules/jest-get-type": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", @@ -7705,21 +7684,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock-fetch": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jest-mock-fetch/-/jest-mock-fetch-2.0.5.tgz", - "integrity": "sha512-SqjzvjLgehVvrhweq9r2pLxlz3ZwPC2p14yTZFxy+bejE+jDgpV+0aw/oysP088wlFbzEl3TRy0gWIYY25vVEA==", - "dev": true, - "dependencies": { - "jest-mock-promise": "^2.0.2" - } - }, - "node_modules/jest-mock-promise": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jest-mock-promise/-/jest-mock-promise-2.0.2.tgz", - "integrity": "sha512-JD5zcuoagmSJ+Gb05iD8o+Ze/viWLzI7QnrVY3Q8FyDVGUH5aXxZON+u7S9ouAt59sLNR97dSMWGezfTkkb5hg==", - "dev": true - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -10052,12 +10016,6 @@ "asap": "~2.0.6" } }, - "node_modules/promise-polyfill": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", - "dev": true - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -15343,15 +15301,6 @@ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, - "cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -17893,16 +17842,6 @@ "jest-util": "^29.6.1" } }, - "jest-fetch-mock": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", - "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", - "dev": true, - "requires": { - "cross-fetch": "^3.0.4", - "promise-polyfill": "^8.1.3" - } - }, "jest-get-type": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", @@ -18081,21 +18020,6 @@ "jest-util": "^29.6.1" } }, - "jest-mock-fetch": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jest-mock-fetch/-/jest-mock-fetch-2.0.5.tgz", - "integrity": "sha512-SqjzvjLgehVvrhweq9r2pLxlz3ZwPC2p14yTZFxy+bejE+jDgpV+0aw/oysP088wlFbzEl3TRy0gWIYY25vVEA==", - "dev": true, - "requires": { - "jest-mock-promise": "^2.0.2" - } - }, - "jest-mock-promise": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jest-mock-promise/-/jest-mock-promise-2.0.2.tgz", - "integrity": "sha512-JD5zcuoagmSJ+Gb05iD8o+Ze/viWLzI7QnrVY3Q8FyDVGUH5aXxZON+u7S9ouAt59sLNR97dSMWGezfTkkb5hg==", - "dev": true - }, "jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -19919,12 +19843,6 @@ "asap": "~2.0.6" } }, - "promise-polyfill": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", - "dev": true - }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",