github.com/containerd/Containerd@v1.4.13/remotes/docker/config/hosts_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"path/filepath"
    25  	"testing"
    26  
    27  	"github.com/containerd/containerd/log/logtest"
    28  	"github.com/containerd/containerd/remotes/docker"
    29  )
    30  
    31  const allCaps = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
    32  
    33  func TestDefaultHosts(t *testing.T) {
    34  	ctx := logtest.WithT(context.Background(), t)
    35  	resolve := ConfigureHosts(ctx, HostOptions{})
    36  
    37  	for _, tc := range []struct {
    38  		host     string
    39  		expected []docker.RegistryHost
    40  	}{
    41  		{
    42  			host: "docker.io",
    43  			expected: []docker.RegistryHost{
    44  				{
    45  					Scheme:       "https",
    46  					Host:         "registry-1.docker.io",
    47  					Path:         "/v2",
    48  					Capabilities: allCaps,
    49  				},
    50  			},
    51  		},
    52  	} {
    53  		hosts, err := resolve(tc.host)
    54  		if err != nil {
    55  			t.Errorf("[%s] resolve failed: %v", tc.host, err)
    56  			continue
    57  		}
    58  		if len(hosts) != len(tc.expected) {
    59  			t.Errorf("[%s] unexpected number of hosts %d, expected %d", tc.host, len(hosts), len(tc.expected))
    60  			continue
    61  		}
    62  		for j := range hosts {
    63  			if !compareRegistryHost(hosts[j], tc.expected[j]) {
    64  
    65  				t.Errorf("[%s] [%d] unexpected host %v, expected %v", tc.host, j, hosts[j], tc.expected[j])
    66  				break
    67  			}
    68  
    69  		}
    70  
    71  	}
    72  }
    73  
    74  func TestParseHostFile(t *testing.T) {
    75  	ctx := logtest.WithT(context.Background(), t)
    76  
    77  	const testtoml = `
    78  server = "https://test-default.registry"
    79  ca = "/etc/path/default"
    80  [header]
    81    x-custom-1 = "custom header"
    82  
    83  [host."https://mirror.registry"]
    84    capabilities = ["pull"]
    85    ca = "/etc/certs/mirror.pem"
    86    skip_verify = false
    87    [host."https://mirror.registry".header]
    88      x-custom-2 = ["value1", "value2"]
    89  
    90  [host."https://mirror-bak.registry/us"]
    91    capabilities = ["pull"]
    92    skip_verify = true
    93  
    94  [host."http://mirror.registry"]
    95    capabilities = ["pull"]
    96  
    97  [host."https://test-1.registry"]
    98    capabilities = ["pull", "resolve", "push"]
    99    ca = ["/etc/certs/test-1-ca.pem", "/etc/certs/special.pem"]
   100    client = [["/etc/certs/client.cert", "/etc/certs/client.key"],["/etc/certs/client.pem", ""]]
   101  
   102  [host."https://test-2.registry"]
   103    client = "/etc/certs/client.pem"
   104  
   105  [host."https://test-3.registry"]
   106    client = ["/etc/certs/client-1.pem", "/etc/certs/client-2.pem"]
   107  `
   108  	var tb, fb = true, false
   109  	expected := []hostConfig{
   110  		{
   111  			scheme:       "https",
   112  			host:         "mirror.registry",
   113  			path:         "/v2",
   114  			capabilities: docker.HostCapabilityPull,
   115  			caCerts:      []string{filepath.FromSlash("/etc/certs/mirror.pem")},
   116  			skipVerify:   &fb,
   117  			header:       http.Header{"x-custom-2": {"value1", "value2"}},
   118  		},
   119  		{
   120  			scheme:       "https",
   121  			host:         "mirror-bak.registry",
   122  			path:         "/us/v2",
   123  			capabilities: docker.HostCapabilityPull,
   124  			skipVerify:   &tb,
   125  		},
   126  		{
   127  			scheme:       "http",
   128  			host:         "mirror.registry",
   129  			path:         "/v2",
   130  			capabilities: docker.HostCapabilityPull,
   131  		},
   132  		{
   133  			scheme:       "https",
   134  			host:         "test-1.registry",
   135  			path:         "/v2",
   136  			capabilities: allCaps,
   137  			caCerts:      []string{filepath.FromSlash("/etc/certs/test-1-ca.pem"), filepath.FromSlash("/etc/certs/special.pem")},
   138  			clientPairs: [][2]string{
   139  				{filepath.FromSlash("/etc/certs/client.cert"), filepath.FromSlash("/etc/certs/client.key")},
   140  				{filepath.FromSlash("/etc/certs/client.pem"), ""},
   141  			},
   142  		},
   143  		{
   144  			scheme:       "https",
   145  			host:         "test-2.registry",
   146  			path:         "/v2",
   147  			capabilities: allCaps,
   148  			clientPairs: [][2]string{
   149  				{filepath.FromSlash("/etc/certs/client.pem")},
   150  			},
   151  		},
   152  		{
   153  			scheme:       "https",
   154  			host:         "test-3.registry",
   155  			path:         "/v2",
   156  			capabilities: allCaps,
   157  			clientPairs: [][2]string{
   158  				{filepath.FromSlash("/etc/certs/client-1.pem")},
   159  				{filepath.FromSlash("/etc/certs/client-2.pem")},
   160  			},
   161  		},
   162  		{
   163  			scheme:       "https",
   164  			host:         "test-default.registry",
   165  			path:         "/v2",
   166  			capabilities: allCaps,
   167  			caCerts:      []string{filepath.FromSlash("/etc/path/default")},
   168  			header:       http.Header{"x-custom-1": {"custom header"}},
   169  		},
   170  	}
   171  	hosts, err := parseHostsFile(ctx, "", []byte(testtoml))
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  
   176  	defer func() {
   177  		if t.Failed() {
   178  			t.Log("HostConfigs...\nActual:\n" + printHostConfig(hosts) + "Expected:\n" + printHostConfig(expected))
   179  		}
   180  	}()
   181  
   182  	if len(hosts) != len(expected) {
   183  		t.Fatalf("Unexpected number of hosts %d, expected %d", len(hosts), len(expected))
   184  	}
   185  
   186  	for i := range hosts {
   187  		if !compareHostConfig(hosts[i], expected[i]) {
   188  			t.Fatalf("Mismatch at host %d", i)
   189  		}
   190  	}
   191  }
   192  
   193  func compareRegistryHost(j, k docker.RegistryHost) bool {
   194  	if j.Scheme != k.Scheme {
   195  		return false
   196  	}
   197  	if j.Host != k.Host {
   198  		return false
   199  	}
   200  	if j.Path != k.Path {
   201  		return false
   202  	}
   203  	if j.Capabilities != k.Capabilities {
   204  		return false
   205  	}
   206  	// Not comparing TLS configs or authorizations
   207  	return true
   208  }
   209  
   210  func compareHostConfig(j, k hostConfig) bool {
   211  	if j.scheme != k.scheme {
   212  		return false
   213  	}
   214  	if j.host != k.host {
   215  		return false
   216  	}
   217  	if j.path != k.path {
   218  		return false
   219  	}
   220  	if j.capabilities != k.capabilities {
   221  		return false
   222  	}
   223  
   224  	if len(j.caCerts) != len(k.caCerts) {
   225  		return false
   226  	}
   227  	for i := range j.caCerts {
   228  		if j.caCerts[i] != k.caCerts[i] {
   229  			return false
   230  		}
   231  	}
   232  	if len(j.clientPairs) != len(k.clientPairs) {
   233  		return false
   234  	}
   235  	for i := range j.clientPairs {
   236  		if j.clientPairs[i][0] != k.clientPairs[i][0] {
   237  			return false
   238  		}
   239  		if j.clientPairs[i][1] != k.clientPairs[i][1] {
   240  			return false
   241  		}
   242  	}
   243  	if j.skipVerify != nil && k.skipVerify != nil {
   244  		if *j.skipVerify != *k.skipVerify {
   245  			return false
   246  		}
   247  	} else if j.skipVerify != nil || k.skipVerify != nil {
   248  		return false
   249  	}
   250  
   251  	if len(j.header) != len(k.header) {
   252  		return false
   253  	}
   254  	for key := range j.header {
   255  		if len(j.header[key]) != len(k.header[key]) {
   256  			return false
   257  		}
   258  		for i := range j.header[key] {
   259  			if j.header[key][i] != k.header[key][i] {
   260  				return false
   261  			}
   262  		}
   263  	}
   264  
   265  	return true
   266  }
   267  
   268  func printHostConfig(hc []hostConfig) string {
   269  	b := bytes.NewBuffer(nil)
   270  	for i := range hc {
   271  		fmt.Fprintf(b, "\t[%d]\tscheme: %q\n", i, hc[i].scheme)
   272  		fmt.Fprintf(b, "\t\thost: %q\n", hc[i].host)
   273  		fmt.Fprintf(b, "\t\tpath: %q\n", hc[i].path)
   274  		fmt.Fprintf(b, "\t\tcaps: %03b\n", hc[i].capabilities)
   275  		fmt.Fprintf(b, "\t\tca: %#v\n", hc[i].caCerts)
   276  		fmt.Fprintf(b, "\t\tclients: %#v\n", hc[i].clientPairs)
   277  		if hc[i].skipVerify == nil {
   278  			fmt.Fprintf(b, "\t\tskip-verify: %v\n", hc[i].skipVerify)
   279  		} else {
   280  			fmt.Fprintf(b, "\t\tskip-verify: %t\n", *hc[i].skipVerify)
   281  		}
   282  		fmt.Fprintf(b, "\t\theader: %#v\n", hc[i].header)
   283  	}
   284  	return b.String()
   285  }