Skip to content

Commit

Permalink
feat: add a service commnd (#39)
Browse files Browse the repository at this point in the history
* feat: add a service commnd

The following command will install atest as a Linux service. `atest service --action install`.

Set the default service port to be 7070 to avoid confliction

* add unit tests

* add more unit tests
  • Loading branch information
LinuxSuRen committed Apr 14, 2023
1 parent 9ee38ff commit e08c204
Show file tree
Hide file tree
Showing 16 changed files with 248 additions and 36 deletions.
11 changes: 6 additions & 5 deletions cmd/init.go
@@ -1,19 +1,20 @@
package cmd

import (
"github.com/linuxsuren/api-testing/pkg/exec"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/spf13/cobra"
)

type initOption struct {
execer fakeruntime.Execer
kustomization string
waitNamespace string
waitResource string
}

// createInitCommand returns the init command
func createInitCommand() (cmd *cobra.Command) {
opt := &initOption{}
func createInitCommand(execer fakeruntime.Execer) (cmd *cobra.Command) {
opt := &initOption{execer: execer}
cmd = &cobra.Command{
Use: "init",
Long: "Support to init Kubernetes cluster with kustomization, and wait it with command: kubectl wait",
Expand All @@ -30,13 +31,13 @@ func createInitCommand() (cmd *cobra.Command) {

func (o *initOption) runE(cmd *cobra.Command, args []string) (err error) {
if o.kustomization != "" {
if err = exec.RunCommand("kubectl", "apply", "-k", o.kustomization, "--wait=true"); err != nil {
if err = o.execer.RunCommand("kubectl", "apply", "-k", o.kustomization, "--wait=true"); err != nil {
return
}
}

if o.waitNamespace != "" && o.waitResource != "" {
if err = exec.RunCommand("kubectl", "wait", "-n", o.waitNamespace, o.waitResource, "--for", "condition=Available=True", "--timeout=900s"); err != nil {
if err = o.execer.RunCommand("kubectl", "wait", "-n", o.waitNamespace, o.waitResource, "--for", "condition=Available=True", "--timeout=900s"); err != nil {
return
}
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/jsonschema_test.go
Expand Up @@ -6,11 +6,12 @@ import (
"testing"

"github.com/linuxsuren/api-testing/cmd"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/stretchr/testify/assert"
)

func TestJSONSchemaCmd(t *testing.T) {
c := cmd.NewRootCmd()
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})

buf := new(bytes.Buffer)
c.SetOut(buf)
Expand Down
10 changes: 7 additions & 3 deletions cmd/root.go
Expand Up @@ -4,19 +4,23 @@ import (
"os"

"github.com/linuxsuren/api-testing/pkg/version"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)

// NewRootCmd creates the root command
func NewRootCmd() (c *cobra.Command) {
func NewRootCmd(execer fakeruntime.Execer) (c *cobra.Command) {
c = &cobra.Command{
Use: "atest",
Short: "API testing tool",
}
c.SetOut(os.Stdout)
c.Version = version.GetVersion()
c.AddCommand(createInitCommand(),
gRPCServer := grpc.NewServer()
c.AddCommand(createInitCommand(execer),
createRunCommand(), createSampleCmd(),
createServerCmd(), createJSONSchemaCmd())
createServerCmd(gRPCServer), createJSONSchemaCmd(),
createServiceCommand(execer))
return
}
10 changes: 8 additions & 2 deletions cmd/root_test.go
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"

atesting "github.com/linuxsuren/api-testing/pkg/testing"
exec "github.com/linuxsuren/go-fake-runtime"
)

func Test_setRelativeDir(t *testing.T) {
Expand Down Expand Up @@ -43,10 +44,15 @@ func TestCreateRunCommand(t *testing.T) {
cmd := createRunCommand()
assert.Equal(t, "run", cmd.Use)

init := createInitCommand()
init := createInitCommand(exec.FakeExecer{})
assert.Equal(t, "init", init.Use)

server := createServerCmd()
server := createServerCmd(&fakeGRPCServer{})
assert.NotNil(t, server)
assert.Equal(t, "server", server.Use)

root := NewRootCmd(exec.FakeExecer{})
root.SetArgs([]string{"init", "-k=demo.yaml", "--wait-namespace", "demo", "--wait-resource", "demo"})
err := root.Execute()
assert.Nil(t, err)
}
3 changes: 2 additions & 1 deletion cmd/run_test.go
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/h2non/gock"
"github.com/linuxsuren/api-testing/pkg/limit"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -103,7 +104,7 @@ func TestRunCommand(t *testing.T) {
}

func TestRootCmd(t *testing.T) {
c := NewRootCmd()
c := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})
assert.NotNil(t, c)
assert.Equal(t, "atest", c.Use)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/sample_test.go
Expand Up @@ -6,11 +6,12 @@ import (

"github.com/linuxsuren/api-testing/cmd"
"github.com/linuxsuren/api-testing/sample"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/stretchr/testify/assert"
)

func TestSampleCmd(t *testing.T) {
c := cmd.NewRootCmd()
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})

buf := new(bytes.Buffer)
c.SetOut(buf)
Expand Down
26 changes: 22 additions & 4 deletions cmd/server.go
Expand Up @@ -11,20 +11,21 @@ import (
"google.golang.org/grpc"
)

func createServerCmd() (c *cobra.Command) {
opt := &serverOption{}
func createServerCmd(gRPCServer gRPCServer) (c *cobra.Command) {
opt := &serverOption{gRPCServer: gRPCServer}
c = &cobra.Command{
Use: "server",
Short: "Run as a server mode",
RunE: opt.runE,
}
flags := c.Flags()
flags.IntVarP(&opt.port, "port", "p", 9090, "The RPC server port")
flags.IntVarP(&opt.port, "port", "p", 7070, "The RPC server port")
flags.BoolVarP(&opt.printProto, "print-proto", "", false, "Print the proto content and exit")
return
}

type serverOption struct {
gRPCServer gRPCServer
port int
printProto bool
}
Expand All @@ -43,9 +44,26 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
return
}

s := grpc.NewServer()
s := o.gRPCServer
server.RegisterRunnerServer(s, server.NewRemoteServer())
log.Printf("server listening at %v", lis.Addr())
s.Serve(lis)
return
}

type gRPCServer interface {
Serve(lis net.Listener) error
grpc.ServiceRegistrar
}

type fakeGRPCServer struct {
}

// Serve is a fake method
func (s *fakeGRPCServer) Serve(net.Listener) error {
return nil
}

// RegisterService is a fake method
func (s *fakeGRPCServer) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
}
7 changes: 6 additions & 1 deletion cmd/server_test.go
Expand Up @@ -5,6 +5,7 @@ import (
"strings"
"testing"

fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/stretchr/testify/assert"
)

Expand All @@ -30,11 +31,15 @@ func TestPrintProto(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := new(bytes.Buffer)
root := NewRootCmd()
root := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})
root.SetOut(buf)
root.SetArgs(tt.args)
err := root.Execute()
tt.verify(t, buf, err)
})
}

server := createServerCmd(&fakeGRPCServer{})
err := server.Execute()
assert.Nil(t, err)
}
76 changes: 76 additions & 0 deletions cmd/service.go
@@ -0,0 +1,76 @@
// Package cmd provides a service command
package cmd

import (
"fmt"
"os"

fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/spf13/cobra"
)

func createServiceCommand(execer fakeruntime.Execer) (c *cobra.Command) {
opt := &serviceOption{
Execer: execer,
}
c = &cobra.Command{
Use: "service",
Aliases: []string{"s"},
Short: "Install atest as a Linux service",
PreRunE: opt.preRunE,
RunE: opt.runE,
}
flags := c.Flags()
flags.StringVarP(&opt.action, "action", "a", "", "The action of service, support actions: install, start, stop, restart, status")
flags.StringVarP(&opt.scriptPath, "script-path", "", "/lib/systemd/system/atest.service", "The service script file path")
return
}

type serviceOption struct {
action string
scriptPath string
fakeruntime.Execer
}

func (o *serviceOption) preRunE(c *cobra.Command, args []string) (err error) {
if o.Execer.OS() != "linux" {
err = fmt.Errorf("only support on Linux")
}
if o.action == "" && len(args) > 0 {
o.action = args[0]
}
return
}

func (o *serviceOption) runE(c *cobra.Command, args []string) (err error) {
var output string
switch o.action {
case "install", "i":
err = os.WriteFile(o.scriptPath, []byte(script), os.ModeAppend)
case "start":
output, err = o.Execer.RunCommandAndReturn("systemctl", "", "start", "atest")
case "stop":
output, err = o.Execer.RunCommandAndReturn("systemctl", "", "stop", "atest")
case "restart":
output, err = o.Execer.RunCommandAndReturn("systemctl", "", "restart", "atest")
case "status":
output, err = o.Execer.RunCommandAndReturn("systemctl", "", "status", "atest")
default:
err = fmt.Errorf("not support action: '%s'", o.action)
}

if output != "" {
c.Println(output)
}
return
}

var script = `[Unit]
Description=API Testing
[Service]
ExecStart=atest server
[Install]
WantedBy=multi-user.target
`
69 changes: 69 additions & 0 deletions cmd/service_test.go
@@ -0,0 +1,69 @@
package cmd

import (
"bytes"
"os"
"testing"

fakeruntime "github.com/linuxsuren/go-fake-runtime"
"github.com/stretchr/testify/assert"
)

func TestService(t *testing.T) {
root := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})
root.SetArgs([]string{"service", "fake"})
err := root.Execute()
assert.NotNil(t, err)

notLinux := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "fake"})
notLinux.SetArgs([]string{"service", "--action", "install"})
err = notLinux.Execute()
assert.NotNil(t, err)

tmpFile, err := os.CreateTemp(os.TempDir(), "service")
assert.Nil(t, err)
defer func() {
os.RemoveAll(tmpFile.Name())
}()

targetScript := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"})
targetScript.SetArgs([]string{"service", "--action", "install", "--script-path", tmpFile.Name()})
err = targetScript.Execute()
assert.Nil(t, err)
data, err := os.ReadFile(tmpFile.Name())
assert.Nil(t, err)
assert.Equal(t, script, string(data))

tests := []struct {
name string
action string
expectOutput string
}{{
name: "action: start",
action: "start",
expectOutput: "output1",
}, {
name: "action: stop",
action: "stop",
expectOutput: "output2",
}, {
name: "action: restart",
action: "restart",
expectOutput: "output3",
}, {
name: "action: status",
action: "status",
expectOutput: "output4",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := new(bytes.Buffer)
normalRoot := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux", ExpectOutput: tt.expectOutput})
normalRoot.SetOut(buf)
normalRoot.SetArgs([]string{"service", "--action", tt.action})
err = normalRoot.Execute()
assert.Nil(t, err)
assert.Equal(t, tt.expectOutput+"\n", buf.String())
})
}
}
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -28,6 +28,7 @@ require (
github.com/imdario/mergo v0.3.11 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/invopop/jsonschema v0.7.0 // indirect
github.com/linuxsuren/go-fake-runtime v0.0.0-20230413085645-15e77ab55dbd // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -563,6 +563,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/linuxsuren/go-fake-runtime v0.0.0-20230413085645-15e77ab55dbd h1:2Avir30WOgcDqG3sA4hlW4bC4c/tgseAUntPhf5JQ6E=
github.com/linuxsuren/go-fake-runtime v0.0.0-20230413085645-15e77ab55dbd/go.mod h1:zmh6J78hSnWZo68faMA2eKOdaEp8eFbERHi3ZB9xHCQ=
github.com/linuxsuren/unstructured v0.0.1 h1:ilUA8MUYbR6l9ebo/YPV2bKqlf62bzQursDSE+j00iU=
github.com/linuxsuren/unstructured v0.0.1/go.mod h1:KH6aTj+FegzGBzc1vS6mzZx3/duhTUTEVyW5sO7p4as=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
Expand Down
3 changes: 2 additions & 1 deletion main.go
Expand Up @@ -4,10 +4,11 @@ import (
"os"

"github.com/linuxsuren/api-testing/cmd"
exec "github.com/linuxsuren/go-fake-runtime"
)

func main() {
c := cmd.NewRootCmd()
c := cmd.NewRootCmd(exec.DefaultExecer{})
if err := c.Execute(); err != nil {
os.Exit(1)
}
Expand Down

0 comments on commit e08c204

Please sign in to comment.