Skip to content

Commit 13efe7b

Browse files
authored
Merge pull request #48 from factorysh/features/logs
Expose logs using REST
2 parents 0271c7f + 096ba2f commit 13efe7b

File tree

8 files changed

+99
-12
lines changed

8 files changed

+99
-12
lines changed

application/application.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ func New(cfg *conf.Conf) (*Application, error) {
152152
r.Post("/", a.PostTaskHandler)
153153
r.Get("/", a.TaskHandler(false))
154154
r.Get("/volumes/*", a.VolumesHandler(6, false))
155+
r.Get("/logs", a.TaskLogsHandler(false))
155156
})
156157
r.Group(func(r chi.Router) {
157158
r.Use(a.RefererMiddleware)
@@ -164,6 +165,7 @@ func New(cfg *conf.Conf) (*Application, error) {
164165
r.Use(authMiddleware.Middleware())
165166
r.Get("/", a.TaskHandler(true))
166167
r.Get("/volumes/*", a.VolumesHandler(6, true))
168+
r.Get("/logs", a.TaskLogsHandler(true))
167169
})
168170
r.Group(func(r chi.Router) {
169171
r.Use(a.RefererMiddleware)

application/application_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,19 @@ func TestApplication(t *testing.T) {
188188
assert.NoError(t, err)
189189
assert.Contains(t, string(data), "Bob")
190190

191+
req, err = mkRequest(key)
192+
assert.NoError(t, err)
193+
req.Method = http.MethodGet
194+
req.URL, err = url.Parse(fmt.Sprintf("%s/service/demo/group/project/-/master/%s/logs", srvApp.URL, mockupCommit))
195+
assert.NoError(t, err)
196+
r, err = cli.Do(req)
197+
assert.NoError(t, err)
198+
defer r.Body.Close()
199+
assert.Equal(t, 200, r.StatusCode)
200+
data, err = ioutil.ReadAll(r.Body)
201+
assert.NoError(t, err)
202+
assert.Contains(t, string(data), "Bob")
203+
191204
}
192205

193206
func TestApplicationStart(t *testing.T) {

application/task.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"time"
99

10+
"github.com/docker/docker/pkg/stdcopy"
1011
_claims "github.com/factorysh/microdensity/claims"
1112
"github.com/factorysh/microdensity/task"
1213
"github.com/go-chi/chi/v5"
@@ -103,12 +104,12 @@ func (a *Application) addTask(t *task.Task, args map[string]string) error {
103104
return err
104105
}
105106

106-
err = a.storage.Upsert(t)
107+
err = a.queue.Put(t, args)
107108
if err != nil {
108109
return err
109110
}
110111

111-
err = a.queue.Put(t, args)
112+
err = a.storage.Upsert(t)
112113
if err != nil {
113114
return err
114115
}
@@ -180,3 +181,52 @@ func (a *Application) TaskIDHandler(w http.ResponseWriter, r *http.Request) {
180181
return
181182
}
182183
}
184+
185+
// TaskLogsHandler get a logs for a task
186+
func (a *Application) TaskLogsHandler(latest bool) func(http.ResponseWriter, *http.Request) {
187+
188+
return func(w http.ResponseWriter, r *http.Request) {
189+
l := a.logger.With(
190+
zap.String("url", r.URL.String()),
191+
zap.String("service", chi.URLParam(r, "serviceID")),
192+
zap.String("project", chi.URLParam(r, "project")),
193+
zap.String("branch", chi.URLParam(r, "branch")),
194+
zap.String("commit", chi.URLParam(r, "commit")),
195+
)
196+
197+
t, err := a.storage.GetByCommit(
198+
chi.URLParam(r, "serviceID"),
199+
chi.URLParam(r, "project"),
200+
chi.URLParam(r, "branch"),
201+
chi.URLParam(r, "commit"),
202+
latest,
203+
)
204+
205+
if err != nil {
206+
l.Warn("Task get error", zap.Error(err))
207+
w.WriteHeader(http.StatusNotFound)
208+
w.Write([]byte(http.StatusText(http.StatusNotFound)))
209+
return
210+
}
211+
212+
reader, err := t.Logs(r.Context(), false)
213+
if err != nil {
214+
l.Warn("Task log error", zap.Error(err))
215+
w.WriteHeader(http.StatusInternalServerError)
216+
w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
217+
return
218+
}
219+
220+
// just stdout for now
221+
// kudos @ndeloof, @rumpl, @glours
222+
_, err = stdcopy.StdCopy(w, nil, reader)
223+
if err != nil {
224+
l.Error("Task log stdcopy write error", zap.Error(err))
225+
w.WriteHeader(http.StatusInternalServerError)
226+
w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
227+
}
228+
229+
w.Header().Set("Content-Type", "text/plain")
230+
}
231+
232+
}

queue/queue.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ func (q *Queue) Put(item *task.Task, env map[string]string) error {
7171
q.Lock()
7272
defer q.Unlock()
7373

74-
err := q.runner.Prepare(item, env)
74+
runnable, err := q.runner.Prepare(item, env)
7575
if err != nil {
7676
return err
7777
}
7878

79+
item.Run = runnable
80+
7981
q.items.Enqueue(item)
8082

8183
queueAdded.Inc()

run/compose.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ func (c *ComposeRun) runCommand(stdout io.WriteCloser, stderr io.WriteCloser, co
214214
Force: true,
215215
})
216216

217-
l.Info("Run service")
218217
n, err := c.service.RunOneOffContainer(c.runCtx, c.project, api.RunOptions{
219218
Name: fmt.Sprintf("%s_%s_%v", c.project.Name, c.run, c.id),
220219
Service: c.run,

run/compose_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,4 @@ func TestCompose(t *testing.T) {
8585
out, err = ioutil.ReadAll(buff)
8686
assert.NoError(t, err)
8787
assert.True(t, strings.HasPrefix(string(out), "8.8.8.8"))
88-
8988
}

run/run.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,26 @@ func NewRunner(servicesDir string, volumesRoot string, hosts []string) (*Runner,
4848
// Prepare the run
4949
// Prepare is synchronous, in order to raise an error in the REST endpoint.
5050
// Prepare checks volumes stuff.
51-
func (r *Runner) Prepare(t *task.Task, env map[string]string) error {
51+
func (r *Runner) Prepare(t *task.Task, env map[string]string) (string, error) {
5252
if t.Id == uuid.Nil {
53-
return fmt.Errorf("task requires an ID to be prepared")
53+
return "", fmt.Errorf("task requires an ID to be prepared")
5454
}
5555

5656
if _, found := r.tasks[t.Id]; found {
57-
return fmt.Errorf("task with id `%s` already prepared", t.Id)
57+
return "", fmt.Errorf("task with id `%s` already prepared", t.Id)
5858
}
5959

6060
runnable, err := NewComposeRun(fmt.Sprintf("%s/%s", r.servicesDir, t.Service))
6161
if err != nil {
62-
return err
62+
return "", err
6363
}
6464

6565
err = runnable.Prepare(env,
6666
r.volumes.Path(t.Service, t.Project, t.Branch, t.Id.String()),
6767
t.Id,
6868
r.hosts)
6969
if err != nil {
70-
return err
70+
return "", err
7171
}
7272

7373
r.tasks[t.Id] = &Context{
@@ -77,7 +77,7 @@ func (r *Runner) Prepare(t *task.Task, env map[string]string) error {
7777
run: runnable,
7878
}
7979

80-
return nil
80+
return runnable.run, nil
8181
}
8282

8383
func (r *Runner) Run(t *task.Task) (int, error) {

task/task.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package task
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
7+
"io"
68
"regexp"
79
"time"
810

11+
"github.com/docker/docker/api/types"
12+
"github.com/docker/docker/client"
913
"github.com/google/uuid"
1014
)
1115

@@ -30,7 +34,9 @@ func (s State) String() string {
3034
}
3135

3236
type Task struct {
33-
Id uuid.UUID `json:"id"`
37+
Id uuid.UUID `json:"id"`
38+
// Run is used to save the name of the main/master container for this service
39+
Run string `json:"run"`
3440
Service string `json:"service"`
3541
Project string `json:"project"`
3642
Branch string `json:"branch"`
@@ -55,3 +61,19 @@ func (t *Task) Validate() error {
5561
}
5662
return nil
5763
}
64+
65+
// Logs steam logs of the current run
66+
func (t *Task) Logs(ctx context.Context, follow bool) (io.ReadCloser, error) {
67+
mainName := fmt.Sprintf("%s_%s_%v", t.Service, t.Run, t.Id)
68+
docker, err := client.NewClientWithOpts(client.FromEnv)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
return docker.ContainerLogs(ctx, mainName, types.ContainerLogsOptions{
74+
ShowStdout: true,
75+
ShowStderr: true,
76+
Timestamps: true,
77+
Follow: follow,
78+
})
79+
}

0 commit comments

Comments
 (0)