github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/client/client_test.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"net/http"
     6  	"net/url"
     7  	"os"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/docker/api"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/internal/testutil"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  func TestNewEnvClient(t *testing.T) {
    19  	if runtime.GOOS == "windows" {
    20  		t.Skip("skipping unix only test for windows")
    21  	}
    22  	cases := []struct {
    23  		envs            map[string]string
    24  		expectedError   string
    25  		expectedVersion string
    26  	}{
    27  		{
    28  			envs:            map[string]string{},
    29  			expectedVersion: api.DefaultVersion,
    30  		},
    31  		{
    32  			envs: map[string]string{
    33  				"DOCKER_CERT_PATH": "invalid/path",
    34  			},
    35  			expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory",
    36  		},
    37  		{
    38  			envs: map[string]string{
    39  				"DOCKER_CERT_PATH": "testdata/",
    40  			},
    41  			expectedVersion: api.DefaultVersion,
    42  		},
    43  		{
    44  			envs: map[string]string{
    45  				"DOCKER_CERT_PATH":  "testdata/",
    46  				"DOCKER_TLS_VERIFY": "1",
    47  			},
    48  			expectedVersion: api.DefaultVersion,
    49  		},
    50  		{
    51  			envs: map[string]string{
    52  				"DOCKER_CERT_PATH": "testdata/",
    53  				"DOCKER_HOST":      "https://notaunixsocket",
    54  			},
    55  			expectedVersion: api.DefaultVersion,
    56  		},
    57  		{
    58  			envs: map[string]string{
    59  				"DOCKER_HOST": "host",
    60  			},
    61  			expectedError: "unable to parse docker host `host`",
    62  		},
    63  		{
    64  			envs: map[string]string{
    65  				"DOCKER_HOST": "invalid://url",
    66  			},
    67  			expectedVersion: api.DefaultVersion,
    68  		},
    69  		{
    70  			envs: map[string]string{
    71  				"DOCKER_API_VERSION": "anything",
    72  			},
    73  			expectedVersion: "anything",
    74  		},
    75  		{
    76  			envs: map[string]string{
    77  				"DOCKER_API_VERSION": "1.22",
    78  			},
    79  			expectedVersion: "1.22",
    80  		},
    81  	}
    82  
    83  	env := envToMap()
    84  	defer mapToEnv(env)
    85  	for _, c := range cases {
    86  		mapToEnv(env)
    87  		mapToEnv(c.envs)
    88  		apiclient, err := NewEnvClient()
    89  		if c.expectedError != "" {
    90  			assert.Error(t, err)
    91  			assert.Equal(t, c.expectedError, err.Error())
    92  		} else {
    93  			assert.NoError(t, err)
    94  			version := apiclient.ClientVersion()
    95  			assert.Equal(t, c.expectedVersion, version)
    96  		}
    97  
    98  		if c.envs["DOCKER_TLS_VERIFY"] != "" {
    99  			// pedantic checking that this is handled correctly
   100  			tr := apiclient.client.Transport.(*http.Transport)
   101  			assert.NotNil(t, tr.TLSClientConfig)
   102  			assert.Equal(t, tr.TLSClientConfig.InsecureSkipVerify, false)
   103  		}
   104  	}
   105  }
   106  
   107  func TestGetAPIPath(t *testing.T) {
   108  	testcases := []struct {
   109  		version  string
   110  		path     string
   111  		query    url.Values
   112  		expected string
   113  	}{
   114  		{"", "/containers/json", nil, "/containers/json"},
   115  		{"", "/containers/json", url.Values{}, "/containers/json"},
   116  		{"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"},
   117  		{"1.22", "/containers/json", nil, "/v1.22/containers/json"},
   118  		{"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
   119  		{"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
   120  		{"v1.22", "/containers/json", nil, "/v1.22/containers/json"},
   121  		{"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
   122  		{"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
   123  		{"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"},
   124  	}
   125  
   126  	for _, testcase := range testcases {
   127  		c := Client{version: testcase.version, basePath: "/"}
   128  		actual := c.getAPIPath(testcase.path, testcase.query)
   129  		assert.Equal(t, actual, testcase.expected)
   130  	}
   131  }
   132  
   133  func TestParseHost(t *testing.T) {
   134  	cases := []struct {
   135  		host  string
   136  		proto string
   137  		addr  string
   138  		base  string
   139  		err   bool
   140  	}{
   141  		{"", "", "", "", true},
   142  		{"foobar", "", "", "", true},
   143  		{"foo://bar", "foo", "bar", "", false},
   144  		{"tcp://localhost:2476", "tcp", "localhost:2476", "", false},
   145  		{"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false},
   146  	}
   147  
   148  	for _, cs := range cases {
   149  		p, a, b, e := ParseHost(cs.host)
   150  		if cs.err {
   151  			assert.Error(t, e)
   152  		}
   153  		assert.Equal(t, cs.proto, p)
   154  		assert.Equal(t, cs.addr, a)
   155  		assert.Equal(t, cs.base, b)
   156  	}
   157  }
   158  
   159  func TestParseHostURL(t *testing.T) {
   160  	testcases := []struct {
   161  		host        string
   162  		expected    *url.URL
   163  		expectedErr string
   164  	}{
   165  		{
   166  			host:        "",
   167  			expectedErr: "unable to parse docker host",
   168  		},
   169  		{
   170  			host:        "foobar",
   171  			expectedErr: "unable to parse docker host",
   172  		},
   173  		{
   174  			host:     "foo://bar",
   175  			expected: &url.URL{Scheme: "foo", Host: "bar"},
   176  		},
   177  		{
   178  			host:     "tcp://localhost:2476",
   179  			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"},
   180  		},
   181  		{
   182  			host:     "tcp://localhost:2476/path",
   183  			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"},
   184  		},
   185  	}
   186  
   187  	for _, testcase := range testcases {
   188  		actual, err := ParseHostURL(testcase.host)
   189  		if testcase.expectedErr != "" {
   190  			testutil.ErrorContains(t, err, testcase.expectedErr)
   191  		}
   192  		assert.Equal(t, testcase.expected, actual)
   193  	}
   194  }
   195  
   196  func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
   197  	env := envToMap()
   198  	defer mapToEnv(env)
   199  
   200  	envMap := map[string]string{
   201  		"DOCKER_HOST":        "",
   202  		"DOCKER_API_VERSION": "",
   203  		"DOCKER_TLS_VERIFY":  "",
   204  		"DOCKER_CERT_PATH":   "",
   205  	}
   206  	mapToEnv(envMap)
   207  
   208  	client, err := NewEnvClient()
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	assert.Equal(t, client.version, api.DefaultVersion)
   213  
   214  	expected := "1.22"
   215  	os.Setenv("DOCKER_API_VERSION", expected)
   216  	client, err = NewEnvClient()
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	assert.Equal(t, expected, client.version)
   221  }
   222  
   223  // TestNegotiateAPIVersionEmpty asserts that client.Client can
   224  // negotiate a compatible APIVersion when omitted
   225  func TestNegotiateAPIVersionEmpty(t *testing.T) {
   226  	env := envToMap()
   227  	defer mapToEnv(env)
   228  
   229  	envMap := map[string]string{
   230  		"DOCKER_API_VERSION": "",
   231  	}
   232  	mapToEnv(envMap)
   233  
   234  	client, err := NewEnvClient()
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	ping := types.Ping{
   240  		APIVersion:   "",
   241  		OSType:       "linux",
   242  		Experimental: false,
   243  	}
   244  
   245  	// set our version to something new
   246  	client.version = "1.25"
   247  
   248  	// if no version from server, expect the earliest
   249  	// version before APIVersion was implemented
   250  	expected := "1.24"
   251  
   252  	// test downgrade
   253  	client.NegotiateAPIVersionPing(ping)
   254  	assert.Equal(t, expected, client.version)
   255  }
   256  
   257  // TestNegotiateAPIVersion asserts that client.Client can
   258  // negotiate a compatible APIVersion with the server
   259  func TestNegotiateAPIVersion(t *testing.T) {
   260  	client, err := NewEnvClient()
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  
   265  	expected := "1.21"
   266  
   267  	ping := types.Ping{
   268  		APIVersion:   expected,
   269  		OSType:       "linux",
   270  		Experimental: false,
   271  	}
   272  
   273  	// set our version to something new
   274  	client.version = "1.22"
   275  
   276  	// test downgrade
   277  	client.NegotiateAPIVersionPing(ping)
   278  	assert.Equal(t, expected, client.version)
   279  
   280  	// set the client version to something older, and verify that we keep the
   281  	// original setting.
   282  	expected = "1.20"
   283  	client.version = expected
   284  	client.NegotiateAPIVersionPing(ping)
   285  	assert.Equal(t, expected, client.version)
   286  
   287  }
   288  
   289  // TestNegotiateAPIVersionOverride asserts that we honor
   290  // the environment variable DOCKER_API_VERSION when negotianing versions
   291  func TestNegotiateAPVersionOverride(t *testing.T) {
   292  	env := envToMap()
   293  	defer mapToEnv(env)
   294  
   295  	envMap := map[string]string{
   296  		"DOCKER_API_VERSION": "9.99",
   297  	}
   298  	mapToEnv(envMap)
   299  
   300  	client, err := NewEnvClient()
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  
   305  	ping := types.Ping{
   306  		APIVersion:   "1.24",
   307  		OSType:       "linux",
   308  		Experimental: false,
   309  	}
   310  
   311  	expected := envMap["DOCKER_API_VERSION"]
   312  
   313  	// test that we honored the env var
   314  	client.NegotiateAPIVersionPing(ping)
   315  	assert.Equal(t, expected, client.version)
   316  }
   317  
   318  // mapToEnv takes a map of environment variables and sets them
   319  func mapToEnv(env map[string]string) {
   320  	for k, v := range env {
   321  		os.Setenv(k, v)
   322  	}
   323  }
   324  
   325  // envToMap returns a map of environment variables
   326  func envToMap() map[string]string {
   327  	env := make(map[string]string)
   328  	for _, e := range os.Environ() {
   329  		kv := strings.SplitAfterN(e, "=", 2)
   330  		env[kv[0]] = kv[1]
   331  	}
   332  
   333  	return env
   334  }
   335  
   336  type roundTripFunc func(*http.Request) (*http.Response, error)
   337  
   338  func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
   339  	return rtf(req)
   340  }
   341  
   342  type bytesBufferClose struct {
   343  	*bytes.Buffer
   344  }
   345  
   346  func (bbc bytesBufferClose) Close() error {
   347  	return nil
   348  }
   349  
   350  func TestClientRedirect(t *testing.T) {
   351  	client := &http.Client{
   352  		CheckRedirect: CheckRedirect,
   353  		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
   354  			if req.URL.String() == "/bla" {
   355  				return &http.Response{StatusCode: 404}, nil
   356  			}
   357  			return &http.Response{
   358  				StatusCode: 301,
   359  				Header:     map[string][]string{"Location": {"/bla"}},
   360  				Body:       bytesBufferClose{bytes.NewBuffer(nil)},
   361  			}, nil
   362  		}),
   363  	}
   364  
   365  	cases := []struct {
   366  		httpMethod  string
   367  		expectedErr error
   368  		statusCode  int
   369  	}{
   370  		{http.MethodGet, nil, 301},
   371  		{http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301},
   372  		{http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301},
   373  		{http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301},
   374  	}
   375  
   376  	for _, tc := range cases {
   377  		req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil)
   378  		assert.NoError(t, err)
   379  		resp, err := client.Do(req)
   380  		assert.Equal(t, tc.expectedErr, err)
   381  		assert.Equal(t, tc.statusCode, resp.StatusCode)
   382  	}
   383  }