diff --git a/go.mod b/go.mod index 6bcdccb8..bc085d0d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect github.com/docker/go-units v0.3.3 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/eclipse/paho.mqtt.golang v1.1.1 + github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/facchinm/service v0.0.0-20180209083557-5cffdea7926c github.com/fsnotify/fsnotify v1.4.7 github.com/go-ole/go-ole v1.2.1 // indirect @@ -52,8 +52,8 @@ require ( github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/sirupsen/logrus v1.1.0 // indirect github.com/stretchr/testify v1.3.0 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20190311183353-d8887717615a + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect google.golang.org/grpc v1.29.1 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index c5258de3..878634e6 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ github.com/docker/cli v0.0.0-20180905184309-44371c7c34d5/go.mod h1:JLrzqnKDaYBop github.com/docker/cli v17.12.1-ce-rc2+incompatible h1:ESUycEAqvFuLglAHkUW66rCc2djYtd3i1x231svLq9o= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible h1:W1rhCpxfWMU0CaZSFWpmWfmB68zYZVksig+VC1ZbgI4= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v17.12.0-ce-rc1.0.20180822115147-a0385f7ad7f8+incompatible h1:70eRy5NQXxf1cDCjGWCafnNZPMlqI4YRU48QIbAI8tw= github.com/docker/docker v17.12.0-ce-rc1.0.20180822115147-a0385f7ad7f8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g= @@ -43,8 +44,8 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/eclipse/paho.mqtt.golang v1.1.1 h1:iPJYXJLaViCshRTW/PSqImSS6HJ2Rf671WR0bXZ2GIU= -github.com/eclipse/paho.mqtt.golang v1.1.1/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -133,7 +134,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -143,6 +145,9 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -152,6 +157,9 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTu golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= diff --git a/handlers_containers.go b/handlers_containers.go index 9bb04647..021b78b0 100644 --- a/handlers_containers.go +++ b/handlers_containers.go @@ -97,9 +97,7 @@ func (s *Status) ContainersPsEvent(client mqtt.Client, msg mqtt.Message) { return } - if !s.SendInfo(s.topicPertinence+"/containers/ps", string(data)+"\n") { - fmt.Println("error sending info") - } + s.SendInfo(s.topicPertinence+"/containers/ps", string(data)+"\n") } // ContainersListImagesEvent implements docker images @@ -129,15 +127,15 @@ func (s *Status) ContainersListImagesEvent(client mqtt.Client, msg mqtt.Message) return } - s.Info("/containers/images", string(data)+"\n") + s.SendInfo(s.topicPertinence+"/containers/images", string(data)+"\n") } -// ContainersListImagesEvent implements docker images +// ContainersRenameEvent implements docker rename func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { cnPayload := ChangeNamePayload{} err := json.Unmarshal(msg.Payload(), &cnPayload) if err != nil { - s.Error("/containers/action", errors.Wrapf(err, "unmarshal %s", msg.Payload())) + s.Error("/containers/rename", errors.Wrapf(err, "unmarshal %s", msg.Payload())) return } err = s.dockerClient.ContainerRename(context.Background(), cnPayload.ContainerID, cnPayload.ContainerName) @@ -153,7 +151,7 @@ func (s *Status) ContainersRenameEvent(client mqtt.Client, msg mqtt.Message) { return } - s.Info("/containers/rename", string(data)+"\n") + s.SendInfo(s.topicPertinence+"/containers/rename", string(data)+"\n") } // ContainersActionEvent implements docker container action like run, start and stop, remove @@ -277,8 +275,7 @@ func (s *Status) ContainersActionEvent(client mqtt.Client, msg mqtt.Message) { return } - s.Info("/containers/action", string(data)+"\n") - + s.SendInfo("/containers/action", string(data)+"\n") } // ConfigureRegistryAuth manages registry authentication usage flow diff --git a/handlers_containers_functional_test.go b/handlers_containers_functional_test.go index f10b153d..7047e0a5 100644 --- a/handlers_containers_functional_test.go +++ b/handlers_containers_functional_test.go @@ -4,6 +4,9 @@ package main import ( "encoding/json" + "io" + "io/ioutil" + "log" "os" "os/exec" "strings" @@ -11,9 +14,13 @@ import ( "time" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" docker "github.com/docker/docker/client" mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) // NewMqttTestClientLocal creates mqtt client in localhost:1883 @@ -49,29 +56,30 @@ func setupAndRun(m *testing.M) int { ts.appStatus.dockerClient, _ = docker.NewClientWithOpts(docker.WithVersion("1.38")) ts.appStatus.mqttClient = mqtt.NewClient(mqtt.NewClientOptions().AddBroker("tcp://localhost:1883").SetClientID("arduino-connector")) + if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { + log.Fatal(token.Error()) + } defer ts.appStatus.mqttClient.Disconnect(100) return m.Run() } -func TestDockerPsApi(t *testing.T) { - if token := ts.appStatus.mqttClient.Connect(); token.Wait() && token.Error() != nil { - t.Fatal(token.Error()) +func execCmd(cmd string) []string { + c := exec.Command("bash", "-c", cmd) + out, err := c.CombinedOutput() + if err != nil { + return []string{} } + lines := strings.Split(string(out), "\n") + return lines[1 : len(lines)-1] +} + +func TestDockerPsApi(t *testing.T) { subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/ps/post", ts.appStatus, ts.appStatus.ContainersPsEvent, false) resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/ps", "{}", 50*time.Millisecond) - // ask Docker about containers effectively running - cmd := exec.Command("bash", "-c", "docker ps -a") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatal(err) - } - - lines := strings.Split(string(out), "\n") - // Remove the first line (command output header) and the last line (empty line) - lines = lines[1 : len(lines)-1] + lines := execCmd("docker ps -a") // Take json without INFO tag resp = strings.TrimPrefix(resp, "INFO: ") @@ -87,3 +95,380 @@ func TestDockerPsApi(t *testing.T) { assert.True(t, strings.HasPrefix(result[i].ID, containerId)) } } + +func TestDockerListImagesApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/images/post", ts.appStatus, ts.appStatus.ContainersListImagesEvent, false) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/images", "{}", 50*time.Millisecond) + + lines := execCmd("docker images -a") + + // Take json without INFO tag + resp = strings.TrimPrefix(resp, "INFO: ") + resp = strings.TrimSuffix(resp, "\n\n") + var result []types.ImageSummary + if err := json.Unmarshal([]byte(resp), &result); err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(result), len(lines)) +} + +func TestDockerRenameApi(t *testing.T) { + // download an alpine image from library to use as test + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "docker.io/library/alpine", types.ImagePullOptions{}) + if err != nil { + t.Fatal(err) + } + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + defer func() { + reader.Close() + + filters := filters.NewArgs(filters.Arg("reference", "alpine")) + images, errImagels := ts.appStatus.dockerClient.ImageList(context.Background(), types.ImageListOptions{Filters: filters}) + if errImagels != nil { + t.Fatal(errImagels) + } + + if _, errImageRemove := ts.appStatus.dockerClient.ImageRemove(context.Background(), images[0].ID, types.ImageRemoveOptions{}); errImageRemove != nil { + t.Fatal(errImageRemove) + } + }() + + // create a test container from downloaded image + createContResp, err := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if err != nil { + t.Fatal(err) + } + defer func() { + if err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), createContResp.ID, types.ContainerRemoveOptions{}); err != nil { + t.Fatal(err) + } + }() + + cnPayload := ChangeNamePayload{ + ContainerID: createContResp.ID, + ContainerName: "newname", + } + data, err := json.Marshal(cnPayload) + if err != nil { + t.Fatal(err) + } + + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/rename/post", ts.appStatus, ts.appStatus.ContainersRenameEvent, true) + resp := ts.ui.MqttSendAndReceiveTimeout(t, "/containers/rename", string(data), 250*time.Millisecond) + + lines := execCmd("docker container ls -a") + + // Take json without INFO tag + resp = strings.TrimPrefix(resp, "INFO: ") + resp = strings.TrimSuffix(resp, "\n\n") + var result ChangeNamePayload + if err := json.Unmarshal([]byte(resp), &result); err != nil { + t.Fatal(err) + } + + assert.Equal(t, cnPayload, result) + + // find test container through its ID and check its name + for _, line := range lines { + tokens := strings.Fields(line) + if strings.HasPrefix(result.ContainerID, tokens[0]) { + assert.Equal(t, result.ContainerName, tokens[len(tokens)-1]) + return + } + } + + t.Fatalf("no container with ID %s has been found\n", result.ContainerID) +} + +func TestDockerActionRunApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + testContainer := "test-container" + payload := map[string]interface{}{"action": "run", "image": "alpine", "name": testContainer} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) + + lines := execCmd("docker ps") + + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { + foundTestContainerRunning = true + } + } + + assert.True(t, foundTestContainerRunning) + + defer func() { + timeout := 1 * time.Millisecond + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() +} + +func TestDockerActionStopApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + testContainer := "test-container" + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + err = ts.appStatus.dockerClient.ContainerRename(context.Background(), createContResp.ID, testContainer) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerStart(context.Background(), testContainer, types.ContainerStartOptions{}) + if err != nil { + t.Error(err) + } + + payload := map[string]interface{}{"action": "stop", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) + + lines := execCmd("docker ps") + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { + foundTestContainerRunning = true + } + } + + assert.False(t, foundTestContainerRunning) + + defer func() { + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() +} + +func TestDockerActionStartApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + testContainer := "test-container" + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + err = ts.appStatus.dockerClient.ContainerRename(context.Background(), createContResp.ID, testContainer) + if err != nil { + t.Error(err) + } + + payload := map[string]interface{}{"action": "start", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 20*time.Second) + + lines := execCmd("docker ps") + foundTestContainerRunning := false + for _, l := range lines { + if strings.Contains(l, "alpine") && strings.Contains(lines[0], testContainer) { + foundTestContainerRunning = true + } + } + + assert.True(t, foundTestContainerRunning) + + defer func() { + timeout := 1 * time.Millisecond + err = ts.appStatus.dockerClient.ContainerStop(context.Background(), testContainer, &timeout) + if err != nil { + t.Error(err) + } + + err = ts.appStatus.dockerClient.ContainerRemove(context.Background(), testContainer, types.ContainerRemoveOptions{}) + if err != nil { + t.Error(err) + } + + _, err = ts.appStatus.dockerClient.ImageRemove(context.Background(), "alpine", types.ImageRemoveOptions{}) + if err != nil { + t.Error(err) + } + }() +} + +func TestDockerActionRemoveApi(t *testing.T) { + subscribeTopic(ts.appStatus.mqttClient, "0", "/containers/action/post", ts.appStatus, ts.appStatus.ContainersActionEvent, false) + + reader, err := ts.appStatus.dockerClient.ImagePull(context.Background(), "alpine", types.ImagePullOptions{}) + if err != nil { + t.Error(err) + } + + _, err = io.Copy(ioutil.Discard, reader) + if err != nil { + t.Error(err) + } + + createContResp, errCreate := ts.appStatus.dockerClient.ContainerCreate(context.Background(), &container.Config{ + Image: "alpine", + Cmd: []string{"echo", "hello world"}, + }, nil, nil, "") + if errCreate != nil { + t.Error(errCreate) + } + + payload := map[string]interface{}{"action": "remove", "image": "alpine", "id": createContResp.ID} + data, err := json.Marshal(payload) + if err != nil { + t.Error(err) + } + + // Use a very large timeout to avoid failing the test on GitHub Actions environment + ts.ui.MqttSendAndReceiveTimeout(t, "/containers/action", string(data), 300*time.Second) + + lines := execCmd("docker ps -a") + foundTestContainer := false + for _, l := range lines { + if strings.Contains(l, createContResp.ID) { + foundTestContainer = true + } + } + + assert.False(t, foundTestContainer) +} + +type MqttTokenMock struct { + returnErr bool +} + +func (t *MqttTokenMock) Wait() bool { + return true +} + +func (t *MqttTokenMock) WaitTimeout(time.Duration) bool { + return true +} + +func (t *MqttTokenMock) Error() error { + if t.returnErr { + return errors.New("test err") + } + + return nil +} + +type MqttClientMock struct { + errPublished string +} + +func (c *MqttClientMock) IsConnected() bool { + return false +} + +func (c *MqttClientMock) IsConnectionOpen() bool { + return true +} + +func (c *MqttClientMock) Connect() mqtt.Token { + return nil +} + +func (c *MqttClientMock) Disconnect(quiesce uint) { +} + +func (c *MqttClientMock) Publish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token { + payloadStr := payload.(string) + if strings.HasPrefix(payloadStr, "INFO") { + return &MqttTokenMock{returnErr: true} + } + + c.errPublished = payloadStr + return &MqttTokenMock{returnErr: false} +} + +func (c *MqttClientMock) Subscribe(topic string, qos byte, callback mqtt.MessageHandler) mqtt.Token { + return nil +} + +func (c *MqttClientMock) SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token { + return nil +} + +func (c *MqttClientMock) Unsubscribe(topics ...string) mqtt.Token { + return nil +} + +func (c *MqttClientMock) AddRoute(topic string, callback mqtt.MessageHandler) { +} + +func (c *MqttClientMock) OptionsReader() mqtt.ClientOptionsReader { + return mqtt.ClientOptionsReader{} +} + +func TestDockerApiError(t *testing.T) { + ts.appStatus.mqttClient = &MqttClientMock{} + topic := "/container/ps" + ts.appStatus.SendInfo(topic, "error") + mockClient := ts.appStatus.mqttClient.(*MqttClientMock) + assert.Equal(t, mockClient.errPublished, "ERROR: test err\n") +} diff --git a/scripts/functional-tests.sh b/scripts/functional-tests.sh index 91611f79..08b796ce 100755 --- a/scripts/functional-tests.sh +++ b/scripts/functional-tests.sh @@ -5,4 +5,4 @@ set -euo pipefail trap 'kill "$(pidof mosquitto)"' EXIT mosquitto > /dev/null & -go test -v --tags=functional --run="TestDockerPsApi" \ No newline at end of file +go test -v --tags=functional --run="TestDocker" \ No newline at end of file diff --git a/status.go b/status.go index 8c2f38bc..7211b98c 100644 --- a/status.go +++ b/status.go @@ -103,10 +103,10 @@ func (s *Status) Error(topic string, err error) { return } s.messagesSent++ - token := s.mqttClient.Publish("$aws/things/"+s.id+topic, 1, false, "ERROR: "+err.Error()+"\n") + token := s.mqttClient.Publish(s.topicPertinence+topic, 1, false, "ERROR: "+err.Error()+"\n") token.Wait() if debugMqtt { - fmt.Println("MQTT OUT: $aws/things/"+s.id+topic, "ERROR: "+err.Error()+"\n") + fmt.Println("MQTT OUT: "+s.topicPertinence+s.id+topic, "ERROR: "+err.Error()+"\n") } } @@ -125,22 +125,20 @@ func (s *Status) Info(topic, msg string) bool { } // SendInfo send information to a specific topic -func (s *Status) SendInfo(topic, msg string) bool { +func (s *Status) SendInfo(topic, msg string) { if s.mqttClient == nil { - return false + return } s.messagesSent++ - if token := s.mqttClient.Publish(topic, 0, false, "INFO: "+msg+"\n"); token.Wait() && token.Error() != nil { - fmt.Println(token.Error()) + if token := s.mqttClient.Publish(s.topicPertinence+topic, 0, false, "INFO: "+msg+"\n"); token.Wait() && token.Error() != nil { + s.Error(topic, token.Error()) } if debugMqtt { - fmt.Println("MQTT OUT: "+topic, "INFO: "+msg+"\n") + fmt.Println("MQTT OUT: "+s.topicPertinence+topic, "INFO: "+msg+"\n") } - - return true } // Raw sends a message on the specified topic without further processing diff --git a/tests_helper.go b/tests_helper.go index d3575e7c..41d25c4d 100644 --- a/tests_helper.go +++ b/tests_helper.go @@ -124,5 +124,4 @@ func (tmc *MqttTestClient) MqttSendAndReceiveSync(t *testing.T, topic, request s } wg.Wait() return response - }