Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit d8eeca5

Browse files
committed
Merge pull request #275 from shipyard/container-logs
container logs
2 parents e25a543 + 3092b95 commit d8eeca5

File tree

12 files changed

+178
-3
lines changed

12 files changed

+178
-3
lines changed

cli/cli.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func main() {
2828
app := cli.NewApp()
2929
app.Name = "shipyard"
3030
app.Usage = "manage a shipyard cluster"
31-
app.Version = "2.0.1"
31+
app.Version = "2.0.3"
3232
app.EnableBashCompletion = true
3333
app.Flags = []cli.Flag{}
3434
app.Commands = []cli.Command{
@@ -43,6 +43,7 @@ func main() {
4343
stopCommand,
4444
restartCommand,
4545
scaleCommand,
46+
logsCommand,
4647
destroyCommand,
4748
engineListCommand,
4849
engineAddCommand,

cli/logs.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
8+
"github.com/codegangsta/cli"
9+
"github.com/shipyard/shipyard/client"
10+
)
11+
12+
var logsCommand = cli.Command{
13+
Name: "logs",
14+
Usage: "show container logs",
15+
Description: "logs <id> [--stdout] [--stderr]",
16+
Action: logsAction,
17+
Flags: []cli.Flag{
18+
cli.BoolFlag{
19+
Name: "stdout",
20+
Usage: "show stdout",
21+
},
22+
cli.BoolFlag{
23+
Name: "stderr",
24+
Usage: "show stderr",
25+
},
26+
},
27+
}
28+
29+
func logsAction(c *cli.Context) {
30+
cfg, err := loadConfig()
31+
if err != nil {
32+
logger.Fatal(err)
33+
}
34+
35+
m := client.NewManager(cfg)
36+
ids := c.Args()
37+
if len(ids) == 0 {
38+
logger.Fatal("you must specify an id")
39+
}
40+
id := ids[0]
41+
42+
container, err := m.Container(id)
43+
stdout := c.Bool("stdout")
44+
stderr := c.Bool("stderr")
45+
46+
// if output not specified, use both
47+
if stdout == false && stderr == false {
48+
stdout = true
49+
stderr = true
50+
}
51+
52+
data, err := m.Logs(container, stdout, stderr)
53+
if err != nil {
54+
logger.Fatalf("error reading logs: %s", err)
55+
}
56+
57+
buf := new(bytes.Buffer)
58+
buf.ReadFrom(data)
59+
60+
io.Copy(os.Stdout, buf)
61+
}

client/client.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"io"
89
"io/ioutil"
910
"net/http"
11+
"net/url"
1012

1113
"github.com/citadel/citadel"
1214
"github.com/shipyard/shipyard"
@@ -147,6 +149,37 @@ func (m *Manager) Scale(container *citadel.Container, count int) error {
147149
return nil
148150
}
149151

152+
func (m *Manager) Logs(container *citadel.Container, stdout bool, stderr bool) (io.ReadCloser, error) {
153+
v := url.Values{}
154+
if stdout {
155+
v.Add("stdout", "1")
156+
}
157+
if stderr {
158+
v.Add("stderr", "1")
159+
}
160+
161+
path := fmt.Sprintf("/api/containers/%s/logs?%s", container.ID, v.Encode())
162+
url := m.buildUrl(path)
163+
req, err := http.NewRequest("GET", url, nil)
164+
if err != nil {
165+
return nil, err
166+
}
167+
168+
if m.config.ServiceKey != "" {
169+
req.Header.Add("X-Service-Key", m.config.ServiceKey)
170+
} else {
171+
req.Header.Add("X-Access-Token", fmt.Sprintf("%s:%s", m.config.Username, m.config.Token))
172+
}
173+
req.Header.Set("User-Agent", "shipyard-cli")
174+
175+
resp, err := http.DefaultClient.Do(req)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
return resp.Body, nil
181+
}
182+
150183
func (m *Manager) Engines() ([]*shipyard.Engine, error) {
151184
engines := []*shipyard.Engine{}
152185
resp, err := m.doRequest("/api/engines", "GET", 200, nil)

controller/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/citadel/citadel"
1313
"github.com/codegangsta/negroni"
14+
"github.com/docker/docker/pkg/stdcopy"
1415
"github.com/gorilla/context"
1516
"github.com/gorilla/mux"
1617
"github.com/shipyard/shipyard"
@@ -137,6 +138,25 @@ func stopContainer(w http.ResponseWriter, r *http.Request) {
137138
w.WriteHeader(http.StatusNoContent)
138139
}
139140

141+
func containerLogs(w http.ResponseWriter, r *http.Request) {
142+
vars := mux.Vars(r)
143+
id := vars["id"]
144+
container, err := controllerManager.Container(id)
145+
if err != nil {
146+
http.Error(w, err.Error(), http.StatusInternalServerError)
147+
return
148+
}
149+
150+
data, err := controllerManager.ClusterManager().Logs(container, true, true)
151+
if err != nil {
152+
logger.Errorf("error getting logs for %s: %s", container.ID, err)
153+
http.Error(w, err.Error(), http.StatusInternalServerError)
154+
return
155+
}
156+
157+
stdcopy.StdCopy(w, w, data)
158+
}
159+
140160
func restartContainer(w http.ResponseWriter, r *http.Request) {
141161
vars := mux.Vars(r)
142162
id := vars["id"]
@@ -707,6 +727,7 @@ func main() {
707727
apiRouter.HandleFunc("/api/containers/{id}/stop", stopContainer).Methods("GET")
708728
apiRouter.HandleFunc("/api/containers/{id}/restart", restartContainer).Methods("GET")
709729
apiRouter.HandleFunc("/api/containers/{id}/scale", scaleContainer).Methods("GET")
730+
apiRouter.HandleFunc("/api/containers/{id}/logs", containerLogs).Methods("GET")
710731
apiRouter.HandleFunc("/api/events", events).Methods("GET")
711732
apiRouter.HandleFunc("/api/events", purgeEvents).Methods("DELETE")
712733
apiRouter.HandleFunc("/api/engines", engines).Methods("GET")

controller/manager/manager.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io"
910
"net"
1011
"net/http"
1112
"strings"
@@ -340,6 +341,14 @@ func (m *Manager) Container(id string) (*citadel.Container, error) {
340341
return nil, nil
341342
}
342343

344+
func (m *Manager) Logs(container *citadel.Container, stdout bool, stderr bool) (io.ReadCloser, error) {
345+
data, err := m.clusterManager.Logs(container, stdout, stderr)
346+
if err != nil {
347+
return nil, err
348+
}
349+
return data, nil
350+
}
351+
343352
func (m *Manager) Containers(all bool) ([]*citadel.Container, error) {
344353
return m.clusterManager.ListContainers(all)
345354
}

controller/static/app/app.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ angular.module('shipyard', [
1010
'shipyard.directives',
1111
'angular-flash.service',
1212
'angular-flash.flash-alert-directive',
13-
'angles'
13+
'angles',
14+
'ansiToHtml'
1415
])
1516
.config(['$routeProvider', '$httpProvider', '$provide', 'flashProvider',
1617
function ($routeProvider, $httpProvider, $provide, flashProvider) {
@@ -38,6 +39,10 @@ angular.module('shipyard', [
3839
templateUrl: 'templates/container_details.html',
3940
controller: 'ContainerDetailsController'
4041
});
42+
$routeProvider.when('/containers/:id/logs', {
43+
templateUrl: 'templates/container_logs.html',
44+
controller: 'ContainerLogsController'
45+
});
4146
$routeProvider.when('/engines', {
4247
templateUrl: 'templates/engines.html',
4348
controller: 'EnginesController'

controller/static/app/controllers.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ angular.module('shipyard.controllers', ['ngCookies'])
325325
};
326326
});
327327
})
328+
.controller('ContainerLogsController', function($scope, $location, $routeParams, $http, flash, Container, ansi2html) {
329+
$scope.template = 'templates/container_logs.html';
330+
331+
$http.get('/api/containers/' + $routeParams.id + "/logs").success(function(data){
332+
$scope.logs = ansi2html.toHtml(data.replace(/\n/g, '<br/>'));
333+
});
334+
Container.query({id: $routeParams.id}, function(data){
335+
$scope.container = data;
336+
});
337+
})
328338
.controller('EnginesController', function($scope, Engines) {
329339
$scope.template = 'templates/engines.html';
330340
Engines.query(function(data){

controller/static/app/filters.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ angular.module('shipyard.filters', [])
3737
return s + " MB";
3838
};
3939
})
40+
.filter('unsafe', function ($sce) {
41+
return function(val) {
42+
return $sce.trustAsHtml(val);
43+
};
44+
})
4045
.filter('formatEvent', function () {
4146
return function (e) {
4247
var evt = "";

controller/static/bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"angular-loader": "~1.2.23",
3030
"angular-cookies": "~1.2.23",
3131
"angular-flash": "~0.1.13",
32-
"angles": "*"
32+
"angles": "*",
33+
"ansi-to-html": "jorgeecardona/ansi-to-html"
3334
}
3435
}

controller/static/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<script src="bower_components/angular-flash/dist/angular-flash.min.js"></script>
3030
<script src="bower_components/chartjs/Chart.min.js"></script>
3131
<script src="bower_components/angles/angles.js"></script>
32+
<script src="bower_components/ansi-to-html/ansi2html.js"></script>
3233
<script src="app/app.js"></script>
3334
<script src="app/services.js"></script>
3435
<script src="app/controllers.js"></script>

0 commit comments

Comments
 (0)