a11b3139 |
package docker
import ( |
a4f8a249 |
"bufio"
"bytes" |
8eeff019 |
"code.google.com/p/go.net/websocket" |
dd4aab84 |
"encoding/base64" |
c0d5d596 |
"encoding/json" |
6a55169e |
"expvar" |
b295239d |
"fmt" |
96d1e9bb |
"github.com/dotcloud/docker/archive" |
f37399d2 |
"github.com/dotcloud/docker/auth" |
8e7db043 |
"github.com/dotcloud/docker/pkg/systemd" |
2e69e172 |
"github.com/dotcloud/docker/utils" |
a11b3139 |
"github.com/gorilla/mux" |
57cfe72e |
"io" |
a11e6167 |
"io/ioutil" |
79e91058 |
"log" |
754ed904 |
"mime" |
3adf9ce0 |
"net" |
a11b3139 |
"net/http" |
9f46779d |
"net/http/pprof" |
3adf9ce0 |
"os" |
12c9b9b3 |
"os/exec" |
999a8d72 |
"regexp" |
1aa7f139 |
"strconv" |
b295239d |
"strings" |
a11b3139 |
)
|
082d1420 |
const ( |
e4cb83c5 |
APIVERSION = 1.8 |
082d1420 |
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTHTTPPORT = 4243
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
) |
faae7220 |
|
166eba3e |
type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
57cfe72e |
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack() |
04cd20fa |
if err != nil {
return nil, nil, err
}
// Flush the options to make sure the client sets the raw mode |
57cfe72e |
conn.Write([]byte{})
return conn, conn, nil |
04cd20fa |
}
|
954ecac3 |
//If we don't do this, POST method without Content-type (even with empty body) will fail
func parseForm(r *http.Request) error { |
333bc23f |
if r == nil {
return nil
} |
954ecac3 |
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
return nil
}
|
0f135ad7 |
func parseMultipartForm(r *http.Request) error {
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
return nil
}
|
04cd20fa |
func httpError(w http.ResponseWriter, err error) { |
b7937e26 |
statusCode := http.StatusInternalServerError |
693ff4d2 |
// FIXME: this is brittle and should not be necessary.
// If we need to differentiate between different possible error types, we should
// create appropriate error types with clearly defined meaning.
if strings.Contains(err.Error(), "No such") { |
b7937e26 |
statusCode = http.StatusNotFound |
0c544357 |
} else if strings.HasPrefix(err.Error(), "Bad parameter") { |
b7937e26 |
statusCode = http.StatusBadRequest |
67b20f2c |
} else if strings.HasPrefix(err.Error(), "Conflict") { |
b7937e26 |
statusCode = http.StatusConflict |
468e4c4b |
} else if strings.HasPrefix(err.Error(), "Impossible") { |
b7937e26 |
statusCode = http.StatusNotAcceptable |
90f6bdd6 |
} else if strings.HasPrefix(err.Error(), "Wrong login/password") { |
b7937e26 |
statusCode = http.StatusUnauthorized |
90f6bdd6 |
} else if strings.Contains(err.Error(), "hasn't been activated") { |
b7937e26 |
statusCode = http.StatusForbidden |
0e24db3a |
}
|
ad723bbf |
if err != nil {
utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error()) |
0e24db3a |
http.Error(w, err.Error(), statusCode)
} |
04cd20fa |
}
|
dd49cc45 |
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
|
f7beba3a |
w.Header().Set("Content-Type", "application/json") |
dd49cc45 |
w.WriteHeader(code) |
f7beba3a |
w.Write(b) |
dd49cc45 |
return nil |
f7beba3a |
}
|
0c544357 |
func getBoolParam(value string) (bool, error) { |
da199846 |
if value == "" { |
0c544357 |
return false, nil
} |
1581ed52 |
ret, err := strconv.ParseBool(value)
if err != nil { |
5b3ad002 |
return false, fmt.Errorf("Bad parameter") |
da199846 |
} |
1581ed52 |
return ret, nil |
0c544357 |
}
|
754ed904 |
func matchesContentType(contentType, expectedType string) bool {
mimetype, _, err := mime.ParseMediaType(contentType)
if err != nil { |
ad723bbf |
utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error()) |
754ed904 |
}
return err == nil && mimetype == expectedType
}
|
3dd1e4d5 |
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig) |
b56b2da5 |
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
093b85b7 |
status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) |
3bae188b |
if err != nil {
return err |
3dd1e4d5 |
} |
b56b2da5 |
if status != "" { |
dd49cc45 |
return writeJSON(w, http.StatusOK, &APIAuth{Status: status}) |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
f37399d2 |
|
faae7220 |
func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
de35b346 |
srv.Eng.ServeHTTP(w, r)
return nil |
b56b2da5 |
} |
f37399d2 |
|
faae7220 |
func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
4918769b |
if err := parseForm(r); err != nil {
return err
} |
fdb3de7b |
job := srv.Eng.Job("kill", vars["name"])
if sig := r.Form.Get("signal"); sig != "" {
job.Args = append(job.Args, sig)
}
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
f37399d2 |
|
faae7220 |
func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
9656cdf0 |
job := srv.Eng.Job("export", vars["name"])
if err := job.Stdout.Add(w); err != nil {
return err
}
if err := job.Run(); err != nil { |
8b31d306 |
return err |
b56b2da5 |
} |
7cc08234 |
return nil |
b56b2da5 |
} |
1e357c69 |
|
fd224ee5 |
func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
c7bbe7ca |
|
0c544357 |
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
} |
c423a790 |
filter := r.Form.Get("filter") |
c7bbe7ca |
|
1990c49a |
outs, err := srv.Images(all, filter) |
c423a790 |
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
|
d7928b9a |
if version < 1.7 {
outs2 := []APIImagesOld{}
for _, ctnr := range outs {
outs2 = append(outs2, ctnr.ToLegacy()...)
}
return writeJSON(w, http.StatusOK, outs2)
} |
5e941f1c |
return writeJSON(w, http.StatusOK, outs) |
c423a790 |
}
|
8f647598 |
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if version > 1.6 {
w.WriteHeader(http.StatusNotFound)
return fmt.Errorf("This is now implemented in the client.")
}
if err := srv.ImagesViz(w); err != nil {
return err
}
return nil
}
|
faae7220 |
func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
51e2c179 |
srv.Eng.ServeHTTP(w, r)
return nil |
b56b2da5 |
} |
79e91058 |
|
b5da8164 |
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
f4b41e1a |
sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error { |
b5da8164 |
b, err := json.Marshal(event)
if err != nil { |
8b3519c5 |
return fmt.Errorf("JSON error") |
b5da8164 |
}
_, err = wf.Write(b)
if err != nil { |
8b3519c5 |
// On error, evict the listener |
ad723bbf |
utils.Errorf("%s", err) |
b5da8164 |
srv.Lock() |
2e4d4c9f |
delete(srv.listeners, r.RemoteAddr) |
b5da8164 |
srv.Unlock() |
8b3519c5 |
return err |
2e4d4c9f |
} |
8b3519c5 |
return nil |
2e4d4c9f |
}
if err := parseForm(r); err != nil {
return err
}
listener := make(chan utils.JSONMessage)
srv.Lock()
srv.listeners[r.RemoteAddr] = listener
srv.Unlock()
since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
if err != nil {
since = 0
}
w.Header().Set("Content-Type", "application/json")
wf := utils.NewWriteFlusher(w) |
a963ff5d |
wf.Flush() |
2e4d4c9f |
if since != 0 { |
8b3519c5 |
// If since, send previous events that happened after the timestamp |
abfdaca3 |
for _, event := range srv.GetEvents() { |
2e4d4c9f |
if event.Time >= since { |
8b3519c5 |
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" { |
2e4d4c9f |
continue
}
if err != nil {
return err
}
}
}
} |
7c50221d |
for event := range listener { |
8b3519c5 |
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" { |
2e4d4c9f |
continue
}
if err != nil { |
b5da8164 |
return err
}
}
return nil
}
|
faae7220 |
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"]
outs, err := srv.ImageHistory(name)
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
return writeJSON(w, http.StatusOK, outs) |
b56b2da5 |
} |
b295239d |
|
faae7220 |
func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"]
changesStr, err := srv.ContainerChanges(name)
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
return writeJSON(w, http.StatusOK, changesStr) |
b56b2da5 |
} |
b295239d |
|
11e28842 |
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
eb4a0271 |
if version < 1.4 {
return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
} |
2e797196 |
if vars == nil {
return fmt.Errorf("Missing parameter")
} |
cfec1c3e |
if err := parseForm(r); err != nil {
return err
} |
5e941f1c |
procsStr, err := srv.ContainerTop(vars["name"], r.Form.Get("ps_args")) |
2e797196 |
if err != nil {
return err
} |
dd49cc45 |
return writeJSON(w, http.StatusOK, procsStr) |
2e797196 |
}
|
fd224ee5 |
func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0c544357 |
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
} |
bd04d7d4 |
size, err := getBoolParam(r.Form.Get("size"))
if err != nil {
return err
} |
bc3fa506 |
since := r.Form.Get("since")
before := r.Form.Get("before") |
60ddcaa1 |
n, err := strconv.Atoi(r.Form.Get("limit")) |
b56b2da5 |
if err != nil {
n = -1
} |
b295239d |
|
bd04d7d4 |
outs := srv.Containers(all, size, n, since, before) |
dd49cc45 |
|
9ae5054c |
if version < 1.5 {
outs2 := []APIContainersOld{}
for _, ctnr := range outs { |
5e941f1c |
outs2 = append(outs2, *ctnr.ToLegacy()) |
9ae5054c |
}
|
33e4d736 |
return writeJSON(w, http.StatusOK, outs2) |
b56b2da5 |
} |
5e941f1c |
return writeJSON(w, http.StatusOK, outs) |
b56b2da5 |
} |
04cd20fa |
|
faae7220 |
func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b295239d |
|
e43ff2f6 |
job := srv.Eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag"))
job.Setenv("force", r.Form.Get("force"))
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
}
w.WriteHeader(http.StatusCreated) |
7cc08234 |
return nil |
b56b2da5 |
} |
79512b2a |
|
faae7220 |
func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
5bec9275 |
config := &Config{} |
3acfc600 |
if err := json.NewDecoder(r.Body).Decode(config); err != nil && err != io.EOF { |
ad723bbf |
utils.Errorf("%s", err) |
b56b2da5 |
} |
930ec9f5 |
|
4b5ceb0f |
job := srv.Eng.Job("commit", r.Form.Get("container")) |
930ec9f5 |
job.Setenv("repo", r.Form.Get("repo"))
job.Setenv("tag", r.Form.Get("tag"))
job.Setenv("author", r.Form.Get("author"))
job.Setenv("comment", r.Form.Get("comment"))
job.SetenvJson("config", config)
var id string
job.Stdout.AddString(&id)
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
return writeJSON(w, http.StatusCreated, &APIID{id}) |
b56b2da5 |
} |
79512b2a |
|
152ebeea |
// Creates an image from Pull or from Import |
faae7220 |
func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
6ce475db |
|
b56b2da5 |
src := r.Form.Get("fromSrc")
image := r.Form.Get("fromImage")
tag := r.Form.Get("tag") |
f29e5dc8 |
repo := r.Form.Get("repo") |
cf19be44 |
|
dd4aab84 |
authEncoded := r.Header.Get("X-Registry-Auth") |
fcee6056 |
authConfig := &auth.AuthConfig{} |
dd4aab84 |
if authEncoded != "" {
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { |
fcee6056 |
// for a pull it is not an error if no auth was given |
d04beb7f |
// to increase compatibility with the existing api it is defaulting to be empty |
fcee6056 |
authConfig = &auth.AuthConfig{}
}
} |
c8c7094b |
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0) |
b56b2da5 |
if image != "" { //pull |
093b85b7 |
metaHeaders := map[string][]string{}
for k, v := range r.Header {
if strings.HasPrefix(k, "X-Meta-") {
metaHeaders[k] = v
}
} |
fcee6056 |
if err := srv.ImagePull(image, tag, w, sf, authConfig, metaHeaders, version > 1.3); err != nil { |
c8c7094b |
if sf.Used() { |
5a36efb6 |
w.Write(sf.FormatError(err)) |
c8c7094b |
return nil
} |
f29e5dc8 |
return err |
0b6c79b3 |
} |
b56b2da5 |
} else { //import |
c8c7094b |
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
if sf.Used() { |
5a36efb6 |
w.Write(sf.FormatError(err)) |
c8c7094b |
return nil
} |
f29e5dc8 |
return err |
0b6c79b3 |
} |
b56b2da5 |
} |
7cc08234 |
return nil |
b56b2da5 |
} |
0b6c79b3 |
|
faae7220 |
func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
04cd20fa |
|
b56b2da5 |
term := r.Form.Get("term")
outs, err := srv.ImagesSearch(term)
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
return writeJSON(w, http.StatusOK, outs) |
b56b2da5 |
} |
36b968bb |
|
faae7220 |
func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
36b968bb |
|
b56b2da5 |
url := r.Form.Get("url")
path := r.Form.Get("path") |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"] |
c8c7094b |
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0) |
5957dd90 |
err := srv.ImageInsert(name, url, path, w, sf) |
0f135ad7 |
if err != nil { |
8cc19765 |
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err |
b56b2da5 |
} |
dd49cc45 |
|
5957dd90 |
return nil |
b56b2da5 |
} |
59a6316f |
|
faae7220 |
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
093b85b7 |
metaHeaders := map[string][]string{}
for k, v := range r.Header {
if strings.HasPrefix(k, "X-Meta-") {
metaHeaders[k] = v
}
} |
2f4de386 |
if err := parseForm(r); err != nil {
return err
} |
da3bb9a7 |
authConfig := &auth.AuthConfig{}
|
dd4aab84 |
authEncoded := r.Header.Get("X-Registry-Auth")
if authEncoded != "" {
// the new format is to handle the authConfig as a header
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
// to increase compatibility to existing api it is defaulting to be empty |
da3bb9a7 |
authConfig = &auth.AuthConfig{}
}
} else { |
d04beb7f |
// the old format is supported for compatibility if there was no authConfig header |
da3bb9a7 |
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
return err
}
} |
2f4de386 |
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"] |
c8c7094b |
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0) |
093b85b7 |
if err := srv.ImagePush(name, w, sf, authConfig, metaHeaders); err != nil { |
c8c7094b |
if sf.Used() { |
5a36efb6 |
w.Write(sf.FormatError(err)) |
c8c7094b |
return nil
} |
2f4de386 |
return err
} |
7cc08234 |
return nil |
b56b2da5 |
} |
10c0e990 |
|
7eaa59f6 |
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
name := vars["name"] |
1211065c |
if version > 1.0 {
w.Header().Set("Content-Type", "application/x-tar")
} |
fd7ab143 |
return srv.ImageExport(name, w) |
7eaa59f6 |
}
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
fd7ab143 |
return srv.ImageLoad(r.Body) |
7eaa59f6 |
}
|
faae7220 |
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
0d292440 |
if err := parseForm(r); err != nil {
return nil
} |
a3f6054f |
out := &APIRun{} |
e5f8ab61 |
job := srv.Eng.Job("create", r.Form.Get("name"))
if err := job.DecodeEnv(r.Body); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
3e9575e2 |
resolvConf, err := utils.GetResolvConf()
if err != nil {
return err
} |
e5f8ab61 |
if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { |
7f118519 |
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns)) |
e5f8ab61 |
job.SetenvList("Dns", defaultDns) |
a3f6054f |
} |
e5f8ab61 |
// Read container ID from the first line of stdout |
a4f8a249 |
job.Stdout.AddString(&out.ID) |
e5f8ab61 |
// Read warnings from stderr |
a4f8a249 |
warnings := &bytes.Buffer{}
job.Stderr.Add(warnings) |
e5f8ab61 |
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
a4f8a249 |
// Parse warnings from stderr
scanner := bufio.NewScanner(warnings)
for scanner.Scan() {
out.Warnings = append(out.Warnings, scanner.Text())
} |
e5f8ab61 |
if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit { |
152ebeea |
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") |
b56b2da5 |
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
} |
e5f8ab61 |
if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit { |
152ebeea |
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") |
b56b2da5 |
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
} |
a3f6054f |
|
e5f8ab61 |
if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled { |
10190be5 |
log.Println("Warning: IPv4 forwarding is disabled.")
out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
}
|
f4ba0d42 |
return writeJSON(w, http.StatusCreated, out) |
b56b2da5 |
} |
10c0e990 |
|
faae7220 |
func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
}
t, err := strconv.Atoi(r.Form.Get("t"))
if err != nil || t < 0 {
t = 10
} |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"]
if err := srv.ContainerRestart(name, t); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
f37399d2 |
|
faae7220 |
func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"] |
1cbdaeba |
|
0c544357 |
removeVolume, err := getBoolParam(r.Form.Get("v"))
if err != nil {
return err
} |
1cbdaeba |
removeLink, err := getBoolParam(r.Form.Get("link"))
if err != nil {
return err
} |
ab96da8e |
|
1cbdaeba |
if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
6ce475db |
|
faae7220 |
func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
67b20f2c |
if err := parseForm(r); err != nil {
return err
} |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"] |
c46382ba |
imgs, err := srv.ImageDelete(name, version > 1.1) |
9060b5c2 |
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
9060b5c2 |
if imgs != nil { |
54da339b |
if len(imgs) != 0 { |
dd49cc45 |
return writeJSON(w, http.StatusOK, imgs) |
9060b5c2 |
} |
5e941f1c |
return fmt.Errorf("Conflict, %s wasn't deleted", name) |
9060b5c2 |
} |
5e941f1c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
b295239d |
|
faae7220 |
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
958b4a87 |
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
job := srv.Eng.Job("start", name) |
4fdf11b2 |
// allow a nil body for backwards compatibility
if r.Body != nil { |
754ed904 |
if matchesContentType(r.Header.Get("Content-Type"), "application/json") { |
958b4a87 |
if err := job.DecodeEnv(r.Body); err != nil { |
d8d33e8b |
return err
} |
4fdf11b2 |
}
} |
958b4a87 |
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
b295239d |
|
faae7220 |
func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
dbe1915f |
job := srv.Eng.Job("stop", vars["name"]) |
6ba456ff |
job.Setenv("t", r.Form.Get("t")) |
dbe1915f |
if err := job.Run(); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0862183c |
w.WriteHeader(http.StatusNoContent) |
7cc08234 |
return nil |
b56b2da5 |
} |
b295239d |
|
faae7220 |
func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
3569d080 |
job := srv.Eng.Job("wait", vars["name"])
var statusStr string
job.Stdout.AddString(&statusStr)
if err := job.Run(); err != nil {
return err
}
// Parse a 16-bit encoded integer to map typical unix exit status.
status, err := strconv.ParseInt(statusStr, 10, 16) |
b56b2da5 |
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
3569d080 |
return writeJSON(w, http.StatusOK, &APIWait{StatusCode: int(status)}) |
b56b2da5 |
} |
04cd20fa |
|
70d2123e |
func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
if vars == nil {
return fmt.Errorf("Missing parameter")
} |
73e8a39f |
if err := srv.Eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { |
70d2123e |
return err
}
return nil
}
|
faae7220 |
func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
954ecac3 |
if err := parseForm(r); err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
0c544357 |
logs, err := getBoolParam(r.Form.Get("logs"))
if err != nil {
return err
}
stream, err := getBoolParam(r.Form.Get("stream"))
if err != nil {
return err
}
stdin, err := getBoolParam(r.Form.Get("stdin"))
if err != nil {
return err
}
stdout, err := getBoolParam(r.Form.Get("stdout"))
if err != nil {
return err
}
stderr, err := getBoolParam(r.Form.Get("stderr"))
if err != nil {
return err
}
|
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"]
|
e854b7b2 |
c, err := srv.ContainerInspect(name)
if err != nil { |
e5fa4a49 |
return err
}
|
e854b7b2 |
inStream, outStream, err := hijackServer(w) |
b56b2da5 |
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
5190f7f3 |
defer func() { |
e854b7b2 |
if tcpc, ok := inStream.(*net.TCPConn); ok { |
5190f7f3 |
tcpc.CloseWrite()
} else { |
e854b7b2 |
inStream.Close() |
5190f7f3 |
}
}()
defer func() { |
e854b7b2 |
if tcpc, ok := outStream.(*net.TCPConn); ok { |
5190f7f3 |
tcpc.CloseWrite() |
e854b7b2 |
} else if closer, ok := outStream.(io.Closer); ok { |
5190f7f3 |
closer.Close()
}
}() |
a11b3139 |
|
e854b7b2 |
var errStream io.Writer
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
082d1420 |
if !c.Config.Tty && version >= 1.6 { |
e854b7b2 |
errStream = utils.NewStdWriter(outStream, utils.Stderr)
outStream = utils.NewStdWriter(outStream, utils.Stdout)
} else {
errStream = outStream
}
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, inStream, outStream, errStream); err != nil {
fmt.Fprintf(outStream, "Error: %s\n", err) |
b56b2da5 |
} |
7cc08234 |
return nil |
b56b2da5 |
} |
a4bcf7e1 |
|
166eba3e |
func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
8eeff019 |
if err := parseForm(r); err != nil {
return err
}
logs, err := getBoolParam(r.Form.Get("logs"))
if err != nil {
return err
}
stream, err := getBoolParam(r.Form.Get("stream"))
if err != nil {
return err
}
stdin, err := getBoolParam(r.Form.Get("stdin"))
if err != nil {
return err
}
stdout, err := getBoolParam(r.Form.Get("stdout"))
if err != nil {
return err
}
stderr, err := getBoolParam(r.Form.Get("stderr"))
if err != nil {
return err
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if _, err := srv.ContainerInspect(name); err != nil {
return err
}
|
166eba3e |
h := websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close() |
8eeff019 |
|
e854b7b2 |
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws, ws); err != nil { |
ad723bbf |
utils.Errorf("Error: %s", err) |
166eba3e |
}
})
h.ServeHTTP(w, r) |
8eeff019 |
return nil
}
|
faae7220 |
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"] |
a4bcf7e1 |
|
b56b2da5 |
container, err := srv.ContainerInspect(name)
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
|
5ec2fea6 |
_, err = srv.ImageInspect(name)
if err == nil {
return fmt.Errorf("Conflict between containers and images")
} |
07324a37 |
|
c4c90e9c |
container.readHostConfig()
c := APIContainer{container, container.hostConfig}
return writeJSON(w, http.StatusOK, c) |
b56b2da5 |
} |
a4bcf7e1 |
|
faae7220 |
func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
ff67da9c |
if vars == nil { |
7cc08234 |
return fmt.Errorf("Missing parameter") |
ff67da9c |
} |
b56b2da5 |
name := vars["name"] |
1e357c69 |
|
b56b2da5 |
image, err := srv.ImageInspect(name)
if err != nil { |
7cc08234 |
return err |
b56b2da5 |
} |
dd49cc45 |
|
5ec2fea6 |
_, err = srv.ContainerInspect(name)
if err == nil {
return fmt.Errorf("Conflict between containers and images") |
c2a14bb1 |
} |
dd49cc45 |
|
71d46eaf |
return writeJSON(w, http.StatusOK, image) |
0f312113 |
}
|
a4879901 |
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
90dde9be |
if version < 1.3 {
return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") |
0f135ad7 |
} |
8291f00a |
var (
remoteURL = r.FormValue("remote")
repoName = r.FormValue("t")
rawSuppressOutput = r.FormValue("q")
rawNoCache = r.FormValue("nocache")
rawRm = r.FormValue("rm")
authEncoded = r.Header.Get("X-Registry-Auth")
authConfig = &auth.AuthConfig{}
tag string
)
repoName, tag = utils.ParseRepositoryTag(repoName) |
228091c7 |
if authEncoded != "" {
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
// for a pull it is not an error if no auth was given
// to increase compatibility with the existing api it is defaulting to be empty
authConfig = &auth.AuthConfig{}
}
} |
0f135ad7 |
|
352991bd |
var context io.Reader |
0f135ad7 |
|
a11e6167 |
if remoteURL == "" { |
352991bd |
context = r.Body
} else if utils.IsGIT(remoteURL) {
if !strings.HasPrefix(remoteURL, "git://") {
remoteURL = "https://" + remoteURL |
a11e6167 |
} |
352991bd |
root, err := ioutil.TempDir("", "docker-build-git") |
a11e6167 |
if err != nil { |
d42c10aa |
return err
} |
352991bd |
defer os.RemoveAll(root) |
0f135ad7 |
|
352991bd |
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
} |
0f135ad7 |
|
96d1e9bb |
c, err := archive.Tar(root, archive.Bzip2) |
352991bd |
if err != nil {
return err
}
context = c
} else if utils.IsURL(remoteURL) { |
12180948 |
f, err := utils.Download(remoteURL) |
352991bd |
if err != nil {
return err |
d42c10aa |
} |
352991bd |
defer f.Body.Close()
dockerFile, err := ioutil.ReadAll(f.Body)
if err != nil {
return err
} |
359a6f49 |
c, err := MkBuildContext(string(dockerFile), nil) |
352991bd |
if err != nil {
return err
}
context = c |
0f135ad7 |
} |
474191dd |
suppressOutput, err := getBoolParam(rawSuppressOutput)
if err != nil {
return err
} |
3a123bc4 |
noCache, err := getBoolParam(rawNoCache)
if err != nil {
return err
} |
b7a3fc68 |
rm, err := getBoolParam(rawRm)
if err != nil {
return err
} |
474191dd |
|
de4429f7 |
if version >= 1.8 { |
b04c6466 |
w.Header().Set("Content-Type", "application/json")
} |
de4429f7 |
sf := utils.NewStreamFormatter(version >= 1.8)
b := NewBuildFile(srv,
&StdoutFormater{
Writer: utils.NewWriteFlusher(w),
StreamFormatter: sf,
},
&StderrFormater{
Writer: utils.NewWriteFlusher(w),
StreamFormatter: sf,
}, |
228091c7 |
!suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig) |
d9bce2de |
id, err := b.Build(context)
if err != nil { |
b04c6466 |
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
} |
5bd0437e |
return fmt.Errorf("Error build: %s", err) |
d9bce2de |
}
if repoName != "" { |
a11e6167 |
srv.runtime.repositories.Set(repoName, tag, id, false) |
0f135ad7 |
}
return nil
}
|
5b8cfbe1 |
func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
copyData := &APICopy{} |
d94b1860 |
contentType := r.Header.Get("Content-Type")
if contentType == "application/json" { |
5b8cfbe1 |
if err := json.NewDecoder(r.Body).Decode(copyData); err != nil {
return err
}
} else { |
d94b1860 |
return fmt.Errorf("Content-Type not supported: %s", contentType) |
5b8cfbe1 |
}
if copyData.Resource == "" { |
27159ce6 |
return fmt.Errorf("Path cannot be empty") |
5b8cfbe1 |
}
if copyData.Resource[0] == '/' { |
d94b1860 |
copyData.Resource = copyData.Resource[1:] |
5b8cfbe1 |
}
if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil { |
ad723bbf |
utils.Errorf("%s", err.Error()) |
5b8cfbe1 |
return err
}
return nil
}
|
dd53c457 |
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
w.WriteHeader(http.StatusOK)
return nil
} |
6d5bdff3 |
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") |
393e873d |
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") |
6d5bdff3 |
}
|
166eba3e |
func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// log the request
utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging {
log.Println(r.Method, r.RequestURI)
}
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
if len(userAgent) == 2 && userAgent[1] != VERSION {
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
}
}
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = APIVERSION
} |
1cbdaeba |
if srv.runtime.config.EnableCors { |
166eba3e |
writeCorsHeaders(w, r)
}
if version == 0 || version > APIVERSION {
w.WriteHeader(http.StatusNotFound)
return
}
if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil { |
ad723bbf |
utils.Errorf("Error: %s", err) |
166eba3e |
httpError(w, err)
}
}
}
|
6a55169e |
// Replicated from expvar.go as not public.
func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}
|
9f46779d |
func AttachProfiler(router *mux.Router) { |
6a55169e |
router.HandleFunc("/debug/vars", expvarHandler) |
9f46779d |
router.HandleFunc("/debug/pprof/", pprof.Index)
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
}
|
0a28628c |
func createRouter(srv *Server, logging bool) (*mux.Router, error) { |
b56b2da5 |
r := mux.NewRouter() |
9f46779d |
if os.Getenv("DEBUG") != "" {
AttachProfiler(r)
} |
d639f61e |
m := map[string]map[string]HttpApiFunc{ |
b56b2da5 |
"GET": { |
e2ca600f |
"/events": getEvents, |
166eba3e |
"/info": getInfo, |
e2ca600f |
"/version": getVersion, |
166eba3e |
"/images/json": getImagesJSON, |
8f647598 |
"/images/viz": getImagesViz, |
166eba3e |
"/images/search": getImagesSearch, |
7eaa59f6 |
"/images/{name:.*}/get": getImagesGet, |
166eba3e |
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName, |
e2ca600f |
"/containers/{name:.*}/top": getContainersTop, |
166eba3e |
"/containers/{name:.*}/attach/ws": wsContainersAttach, |
b56b2da5 |
},
"POST": { |
152ebeea |
"/auth": postAuth, |
b56b2da5 |
"/commit": postCommit, |
0f135ad7 |
"/build": postBuild, |
152ebeea |
"/images/create": postImagesCreate, |
1941c791 |
"/images/{name:.*}/insert": postImagesInsert, |
7eaa59f6 |
"/images/load": postImagesLoad, |
1941c791 |
"/images/{name:.*}/push": postImagesPush, |
152ebeea |
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainersKill, |
b56b2da5 |
"/containers/{name:.*}/restart": postContainersRestart,
"/containers/{name:.*}/start": postContainersStart,
"/containers/{name:.*}/stop": postContainersStop,
"/containers/{name:.*}/wait": postContainersWait, |
70d2123e |
"/containers/{name:.*}/resize": postContainersResize, |
b56b2da5 |
"/containers/{name:.*}/attach": postContainersAttach, |
5b8cfbe1 |
"/containers/{name:.*}/copy": postContainersCopy, |
b56b2da5 |
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": deleteImages,
}, |
dd53c457 |
"OPTIONS": {
"": optionsHandler,
}, |
b56b2da5 |
}
for method, routes := range m {
for route, fct := range routes { |
2e69e172 |
utils.Debugf("Registering %s, %s", method, route) |
b56b2da5 |
// NOTE: scope issue, make sure the variables are local and won't be changed
localRoute := route
localFct := fct |
166eba3e |
localMethod := method |
b419699a |
|
166eba3e |
// build the handler function
f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct) |
dd53c457 |
|
166eba3e |
// add the new route |
dd53c457 |
if localRoute == "" {
r.Methods(localMethod).HandlerFunc(f)
} else {
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
} |
131c6ab3 |
} |
b56b2da5 |
} |
8eeff019 |
|
0a28628c |
return r, nil
}
|
359a6f49 |
// ServeRequest processes a single http request to the docker remote api.
// FIXME: refactor this to be part of Server and not require re-creating a new
// router each time. This requires first moving ListenAndServe into Server.
func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error {
router, err := createRouter(srv, false)
if err != nil {
return err
}
// Insert APIVERSION into the request as a convenience
req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
router.ServeHTTP(w, req)
return nil
}
|
3adf9ce0 |
func ListenAndServe(proto, addr string, srv *Server, logging bool) error { |
0a28628c |
r, err := createRouter(srv, logging)
if err != nil {
return err
} |
3adf9ce0 |
l, e := net.Listen(proto, addr)
if e != nil {
return e
}
if proto == "unix" { |
999a8d72 |
if err := os.Chmod(addr, 0660); err != nil {
return err
}
groups, err := ioutil.ReadFile("/etc/group")
if err != nil {
return err
}
re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
gid, err := strconv.Atoi(gidMatch[2])
if err != nil {
return err
}
utils.Debugf("docker group found. gid: %d", gid)
if err := os.Chown(addr, 0, gid); err != nil {
return err
}
} |
3adf9ce0 |
}
httpSrv := http.Server{Addr: addr, Handler: r} |
97088ebe |
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
// Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1") |
3adf9ce0 |
return httpSrv.Serve(l) |
a11b3139 |
} |