Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -6,14 +6,14 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"io" |
| 8 | 8 |
"net/http" |
| 9 |
- "net/url" |
|
| 10 | 9 |
"os" |
| 11 |
- "strings" |
|
| 10 |
+ "runtime" |
|
| 12 | 11 |
|
| 12 |
+ "github.com/docker/docker/api/client/lib" |
|
| 13 | 13 |
"github.com/docker/docker/cli" |
| 14 | 14 |
"github.com/docker/docker/cliconfig" |
| 15 |
+ "github.com/docker/docker/dockerversion" |
|
| 15 | 16 |
"github.com/docker/docker/opts" |
| 16 |
- "github.com/docker/docker/pkg/sockets" |
|
| 17 | 17 |
"github.com/docker/docker/pkg/term" |
| 18 | 18 |
"github.com/docker/docker/pkg/tlsconfig" |
| 19 | 19 |
) |
| ... | ... |
@@ -24,13 +24,6 @@ type DockerCli struct {
|
| 24 | 24 |
// initializing closure |
| 25 | 25 |
init func() error |
| 26 | 26 |
|
| 27 |
- // proto holds the client protocol i.e. unix. |
|
| 28 |
- proto string |
|
| 29 |
- // addr holds the client address. |
|
| 30 |
- addr string |
|
| 31 |
- // basePath holds the path to prepend to the requests |
|
| 32 |
- basePath string |
|
| 33 |
- |
|
| 34 | 27 |
// configFile has the client configuration file |
| 35 | 28 |
configFile *cliconfig.ConfigFile |
| 36 | 29 |
// in holds the input stream and closer (io.ReadCloser) for the client. |
| ... | ... |
@@ -41,11 +34,6 @@ type DockerCli struct {
|
| 41 | 41 |
err io.Writer |
| 42 | 42 |
// keyFile holds the key file as a string. |
| 43 | 43 |
keyFile string |
| 44 |
- // tlsConfig holds the TLS configuration for the client, and will |
|
| 45 |
- // set the scheme to https in NewDockerCli if present. |
|
| 46 |
- tlsConfig *tls.Config |
|
| 47 |
- // scheme holds the scheme of the client i.e. https. |
|
| 48 |
- scheme string |
|
| 49 | 44 |
// inFd holds the file descriptor of the client's STDIN (if valid). |
| 50 | 45 |
inFd uintptr |
| 51 | 46 |
// outFd holds file descriptor of the client's STDOUT (if valid). |
| ... | ... |
@@ -54,6 +42,22 @@ type DockerCli struct {
|
| 54 | 54 |
isTerminalIn bool |
| 55 | 55 |
// isTerminalOut indicates whether the client's STDOUT is a TTY |
| 56 | 56 |
isTerminalOut bool |
| 57 |
+ // client is the http client that performs all API operations |
|
| 58 |
+ client *lib.Client |
|
| 59 |
+ |
|
| 60 |
+ // DEPRECATED OPTIONS TO MAKE THE CLIENT COMPILE |
|
| 61 |
+ // TODO: Remove |
|
| 62 |
+ // proto holds the client protocol i.e. unix. |
|
| 63 |
+ proto string |
|
| 64 |
+ // addr holds the client address. |
|
| 65 |
+ addr string |
|
| 66 |
+ // basePath holds the path to prepend to the requests |
|
| 67 |
+ basePath string |
|
| 68 |
+ // tlsConfig holds the TLS configuration for the client, and will |
|
| 69 |
+ // set the scheme to https in NewDockerCli if present. |
|
| 70 |
+ tlsConfig *tls.Config |
|
| 71 |
+ // scheme holds the scheme of the client i.e. https. |
|
| 72 |
+ scheme string |
|
| 57 | 73 |
// transport holds the client transport instance. |
| 58 | 74 |
transport *http.Transport |
| 59 | 75 |
} |
| ... | ... |
@@ -98,50 +102,35 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 | 100 |
cli.init = func() error {
|
| 101 |
- |
|
| 102 | 101 |
clientFlags.PostParse() |
| 103 |
- |
|
| 104 |
- hosts := clientFlags.Common.Hosts |
|
| 105 |
- |
|
| 106 |
- switch len(hosts) {
|
|
| 107 |
- case 0: |
|
| 108 |
- hosts = []string{os.Getenv("DOCKER_HOST")}
|
|
| 109 |
- case 1: |
|
| 110 |
- // only accept one host to talk to |
|
| 111 |
- default: |
|
| 112 |
- return errors.New("Please specify only one -H")
|
|
| 102 |
+ configFile, e := cliconfig.Load(cliconfig.ConfigDir()) |
|
| 103 |
+ if e != nil {
|
|
| 104 |
+ fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) |
|
| 113 | 105 |
} |
| 106 |
+ cli.configFile = configFile |
|
| 114 | 107 |
|
| 115 |
- defaultHost := opts.DefaultTCPHost |
|
| 116 |
- if clientFlags.Common.TLSOptions != nil {
|
|
| 117 |
- defaultHost = opts.DefaultTLSHost |
|
| 108 |
+ host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ return err |
|
| 118 | 111 |
} |
| 119 | 112 |
|
| 120 |
- var e error |
|
| 121 |
- if hosts[0], e = opts.ParseHost(defaultHost, hosts[0]); e != nil {
|
|
| 122 |
- return e |
|
| 113 |
+ customHeaders := cli.configFile.HTTPHeaders |
|
| 114 |
+ if customHeaders == nil {
|
|
| 115 |
+ customHeaders = map[string]string{}
|
|
| 123 | 116 |
} |
| 117 |
+ customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
|
| 124 | 118 |
|
| 125 |
- protoAddrParts := strings.SplitN(hosts[0], "://", 2) |
|
| 126 |
- cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1] |
|
| 127 |
- |
|
| 128 |
- if cli.proto == "tcp" {
|
|
| 129 |
- // error is checked in pkg/parsers already |
|
| 130 |
- parsed, _ := url.Parse("tcp://" + cli.addr)
|
|
| 131 |
- cli.addr = parsed.Host |
|
| 132 |
- cli.basePath = parsed.Path |
|
| 119 |
+ client, err := lib.NewClient(host, clientFlags.Common.TLSOptions, customHeaders) |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ return err |
|
| 133 | 122 |
} |
| 123 |
+ cli.client = client |
|
| 134 | 124 |
|
| 135 |
- if clientFlags.Common.TLSOptions != nil {
|
|
| 136 |
- cli.scheme = "https" |
|
| 137 |
- var e error |
|
| 138 |
- cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions) |
|
| 139 |
- if e != nil {
|
|
| 140 |
- return e |
|
| 141 |
- } |
|
| 142 |
- } else {
|
|
| 143 |
- cli.scheme = "http" |
|
| 144 |
- } |
|
| 125 |
+ // FIXME: Deprecated, only to keep the old code running. |
|
| 126 |
+ cli.transport = client.HTTPClient.Transport.(*http.Transport) |
|
| 127 |
+ cli.basePath = client.BasePath |
|
| 128 |
+ cli.addr = client.Addr |
|
| 129 |
+ cli.scheme = client.Scheme |
|
| 145 | 130 |
|
| 146 | 131 |
if cli.in != nil {
|
| 147 | 132 |
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) |
| ... | ... |
@@ -150,20 +139,27 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF |
| 150 | 150 |
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) |
| 151 | 151 |
} |
| 152 | 152 |
|
| 153 |
- // The transport is created here for reuse during the client session. |
|
| 154 |
- cli.transport = &http.Transport{
|
|
| 155 |
- TLSClientConfig: cli.tlsConfig, |
|
| 156 |
- } |
|
| 157 |
- sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr) |
|
| 158 |
- |
|
| 159 |
- configFile, e := cliconfig.Load(cliconfig.ConfigDir()) |
|
| 160 |
- if e != nil {
|
|
| 161 |
- fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) |
|
| 162 |
- } |
|
| 163 |
- cli.configFile = configFile |
|
| 164 |
- |
|
| 165 | 153 |
return nil |
| 166 | 154 |
} |
| 167 | 155 |
|
| 168 | 156 |
return cli |
| 169 | 157 |
} |
| 158 |
+ |
|
| 159 |
+func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
|
|
| 160 |
+ switch len(hosts) {
|
|
| 161 |
+ case 0: |
|
| 162 |
+ host = os.Getenv("DOCKER_HOST")
|
|
| 163 |
+ case 1: |
|
| 164 |
+ host = hosts[0] |
|
| 165 |
+ default: |
|
| 166 |
+ return "", errors.New("Please specify only one -H")
|
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ defaultHost := opts.DefaultTCPHost |
|
| 170 |
+ if tlsOptions != nil {
|
|
| 171 |
+ defaultHost = opts.DefaultTLSHost |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ host, err = opts.ParseHost(defaultHost, host) |
|
| 175 |
+ return |
|
| 176 |
+} |
| 170 | 177 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,98 @@ |
| 0 |
+package lib |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "crypto/tls" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "net/http" |
|
| 6 |
+ "net/url" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/api" |
|
| 10 |
+ "github.com/docker/docker/pkg/sockets" |
|
| 11 |
+ "github.com/docker/docker/pkg/tlsconfig" |
|
| 12 |
+ "github.com/docker/docker/pkg/version" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// Client is the API client that performs all operations |
|
| 16 |
+// against a docker server. |
|
| 17 |
+type Client struct {
|
|
| 18 |
+ // proto holds the client protocol i.e. unix. |
|
| 19 |
+ Proto string |
|
| 20 |
+ // addr holds the client address. |
|
| 21 |
+ Addr string |
|
| 22 |
+ // basePath holds the path to prepend to the requests |
|
| 23 |
+ BasePath string |
|
| 24 |
+ // scheme holds the scheme of the client i.e. https. |
|
| 25 |
+ Scheme string |
|
| 26 |
+ // httpClient holds the client transport instance. Exported to keep the old code running. |
|
| 27 |
+ HTTPClient *http.Client |
|
| 28 |
+ // version of the server to talk to. |
|
| 29 |
+ version version.Version |
|
| 30 |
+ // custom http headers configured by users |
|
| 31 |
+ customHTTPHeaders map[string]string |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// NewClient initializes a new API client |
|
| 35 |
+// for the given host. It uses the tlsOptions |
|
| 36 |
+// to decide whether to use a secure connection or not. |
|
| 37 |
+// It also initializes the custom http headers to add to each request. |
|
| 38 |
+func NewClient(host string, tlsOptions *tlsconfig.Options, httpHeaders map[string]string) (*Client, error) {
|
|
| 39 |
+ return NewClientWithVersion(host, api.Version, tlsOptions, httpHeaders) |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// NewClientWithVersion initializes a new API client |
|
| 43 |
+// for the given host and API version. It uses the tlsOptions |
|
| 44 |
+// to decide whether to use a secure connection or not. |
|
| 45 |
+// It also initializes the custom http headers to add to each request. |
|
| 46 |
+func NewClientWithVersion(host string, version version.Version, tlsOptions *tlsconfig.Options, httpHeaders map[string]string) (*Client, error) {
|
|
| 47 |
+ var ( |
|
| 48 |
+ basePath string |
|
| 49 |
+ tlsConfig *tls.Config |
|
| 50 |
+ scheme = "http" |
|
| 51 |
+ protoAddrParts = strings.SplitN(host, "://", 2) |
|
| 52 |
+ proto, addr = protoAddrParts[0], protoAddrParts[1] |
|
| 53 |
+ ) |
|
| 54 |
+ |
|
| 55 |
+ if proto == "tcp" {
|
|
| 56 |
+ parsed, err := url.Parse("tcp://" + addr)
|
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ return nil, err |
|
| 59 |
+ } |
|
| 60 |
+ addr = parsed.Host |
|
| 61 |
+ basePath = parsed.Path |
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ if tlsOptions != nil {
|
|
| 65 |
+ scheme = "https" |
|
| 66 |
+ var err error |
|
| 67 |
+ tlsConfig, err = tlsconfig.Client(*tlsOptions) |
|
| 68 |
+ if err != nil {
|
|
| 69 |
+ return nil, err |
|
| 70 |
+ } |
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ // The transport is created here for reuse during the client session. |
|
| 74 |
+ transport := &http.Transport{
|
|
| 75 |
+ TLSClientConfig: tlsConfig, |
|
| 76 |
+ } |
|
| 77 |
+ sockets.ConfigureTCPTransport(transport, proto, addr) |
|
| 78 |
+ |
|
| 79 |
+ return &Client{
|
|
| 80 |
+ Addr: addr, |
|
| 81 |
+ BasePath: basePath, |
|
| 82 |
+ Scheme: scheme, |
|
| 83 |
+ HTTPClient: &http.Client{Transport: transport},
|
|
| 84 |
+ version: version, |
|
| 85 |
+ customHTTPHeaders: httpHeaders, |
|
| 86 |
+ }, nil |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// getAPIPath returns the versioned request path to call the api. |
|
| 90 |
+// It appends the query parameters to the path if they are not empty. |
|
| 91 |
+func (cli *Client) getAPIPath(p string, query url.Values) string {
|
|
| 92 |
+ apiPath := fmt.Sprintf("%s/v%s%s", cli.BasePath, cli.version, p)
|
|
| 93 |
+ if len(query) > 0 {
|
|
| 94 |
+ apiPath += "?" + query.Encode() |
|
| 95 |
+ } |
|
| 96 |
+ return apiPath |
|
| 97 |
+} |
| 0 | 98 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,155 @@ |
| 0 |
+package lib |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "io/ioutil" |
|
| 8 |
+ "net/http" |
|
| 9 |
+ "net/url" |
|
| 10 |
+ "strings" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/utils" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// ServerResponse is a wrapper for http API responses. |
|
| 16 |
+type ServerResponse struct {
|
|
| 17 |
+ body io.ReadCloser |
|
| 18 |
+ header http.Header |
|
| 19 |
+ statusCode int |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// HEAD sends an http request to the docker API using the method HEAD. |
|
| 23 |
+func (cli *Client) HEAD(path string, query url.Values, headers map[string][]string) (*ServerResponse, error) {
|
|
| 24 |
+ return cli.sendRequest("HEAD", path, query, nil, headers)
|
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// GET sends an http request to the docker API using the method GET. |
|
| 28 |
+func (cli *Client) GET(path string, query url.Values, headers map[string][]string) (*ServerResponse, error) {
|
|
| 29 |
+ return cli.sendRequest("GET", path, query, nil, headers)
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// POST sends an http request to the docker API using the method POST. |
|
| 33 |
+func (cli *Client) POST(path string, query url.Values, body interface{}, headers map[string][]string) (*ServerResponse, error) {
|
|
| 34 |
+ return cli.sendRequest("POST", path, query, body, headers)
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// POSTRaw sends the raw input to the docker API using the method POST. |
|
| 38 |
+func (cli *Client) POSTRaw(path string, query url.Values, body io.Reader, headers map[string][]string) (*ServerResponse, error) {
|
|
| 39 |
+ return cli.sendClientRequest("POST", path, query, body, headers)
|
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// PUT sends an http request to the docker API using the method PUT. |
|
| 43 |
+func (cli *Client) PUT(path string, query url.Values, body interface{}, headers map[string][]string) (*ServerResponse, error) {
|
|
| 44 |
+ return cli.sendRequest("PUT", path, query, body, headers)
|
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+// DELETE sends an http request to the docker API using the method DELETE. |
|
| 48 |
+func (cli *Client) DELETE(path string, query url.Values, headers map[string][]string) (*ServerResponse, error) {
|
|
| 49 |
+ return cli.sendRequest("DELETE", path, query, nil, headers)
|
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func (cli *Client) sendRequest(method, path string, query url.Values, body interface{}, headers map[string][]string) (*ServerResponse, error) {
|
|
| 53 |
+ params, err := encodeData(body) |
|
| 54 |
+ if err != nil {
|
|
| 55 |
+ return nil, err |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if body != nil {
|
|
| 59 |
+ if headers == nil {
|
|
| 60 |
+ headers = make(map[string][]string) |
|
| 61 |
+ } |
|
| 62 |
+ headers["Content-Type"] = []string{"application/json"}
|
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ return cli.sendClientRequest(method, path, query, params, headers) |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func (cli *Client) sendClientRequest(method, path string, query url.Values, in io.Reader, headers map[string][]string) (*ServerResponse, error) {
|
|
| 69 |
+ serverResp := &ServerResponse{
|
|
| 70 |
+ body: nil, |
|
| 71 |
+ statusCode: -1, |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ expectedPayload := (method == "POST" || method == "PUT") |
|
| 75 |
+ if expectedPayload && in == nil {
|
|
| 76 |
+ in = bytes.NewReader([]byte{})
|
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ apiPath := cli.getAPIPath(path, query) |
|
| 80 |
+ req, err := http.NewRequest(method, apiPath, in) |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ return serverResp, err |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ // Add CLI Config's HTTP Headers BEFORE we set the Docker headers |
|
| 86 |
+ // then the user can't change OUR headers |
|
| 87 |
+ for k, v := range cli.customHTTPHeaders {
|
|
| 88 |
+ req.Header.Set(k, v) |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ req.URL.Host = cli.Addr |
|
| 92 |
+ req.URL.Scheme = cli.Scheme |
|
| 93 |
+ |
|
| 94 |
+ if headers != nil {
|
|
| 95 |
+ for k, v := range headers {
|
|
| 96 |
+ req.Header[k] = v |
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ if expectedPayload && req.Header.Get("Content-Type") == "" {
|
|
| 101 |
+ req.Header.Set("Content-Type", "text/plain")
|
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ resp, err := cli.HTTPClient.Do(req) |
|
| 105 |
+ if resp != nil {
|
|
| 106 |
+ serverResp.statusCode = resp.StatusCode |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
|
| 111 |
+ return serverResp, errConnectionFailed |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ if cli.Scheme == "http" && strings.Contains(err.Error(), "malformed HTTP response") {
|
|
| 115 |
+ return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
|
| 116 |
+ } |
|
| 117 |
+ if cli.Scheme == "https" && strings.Contains(err.Error(), "remote error: bad certificate") {
|
|
| 118 |
+ return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
|
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
|
|
| 125 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ return serverResp, err |
|
| 128 |
+ } |
|
| 129 |
+ if len(body) == 0 {
|
|
| 130 |
+ return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
|
|
| 131 |
+ } |
|
| 132 |
+ return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ serverResp.body = resp.Body |
|
| 136 |
+ serverResp.header = resp.Header |
|
| 137 |
+ return serverResp, nil |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+func encodeData(data interface{}) (*bytes.Buffer, error) {
|
|
| 141 |
+ params := bytes.NewBuffer(nil) |
|
| 142 |
+ if data != nil {
|
|
| 143 |
+ if err := json.NewEncoder(params).Encode(data); err != nil {
|
|
| 144 |
+ return nil, err |
|
| 145 |
+ } |
|
| 146 |
+ } |
|
| 147 |
+ return params, nil |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func ensureReaderClosed(response *ServerResponse) {
|
|
| 151 |
+ if response != nil && response.body != nil {
|
|
| 152 |
+ response.body.Close() |
|
| 153 |
+ } |
|
| 154 |
+} |
| ... | ... |
@@ -192,7 +192,14 @@ func Load(configDir string) (*ConfigFile, error) {
|
| 192 | 192 |
} |
| 193 | 193 |
defer file.Close() |
| 194 | 194 |
err = configFile.LegacyLoadFromReader(file) |
| 195 |
- return &configFile, err |
|
| 195 |
+ if err != nil {
|
|
| 196 |
+ return &configFile, err |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ if configFile.HTTPHeaders == nil {
|
|
| 200 |
+ configFile.HTTPHeaders = map[string]string{}
|
|
| 201 |
+ } |
|
| 202 |
+ return &configFile, nil |
|
| 196 | 203 |
} |
| 197 | 204 |
|
| 198 | 205 |
// SaveToWriter encodes and writes out all the authorization information to |