github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/integration/plugin/common/plugin_test.go (about)

     1  package common // import "github.com/docker/docker/integration/plugin/common"
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/containerd/containerd/images"
    18  	"github.com/containerd/containerd/remotes/docker"
    19  	"github.com/docker/docker/api/types"
    20  	"github.com/docker/docker/pkg/jsonmessage"
    21  	"github.com/docker/docker/testutil/daemon"
    22  	"github.com/docker/docker/testutil/fixtures/plugin"
    23  	"github.com/docker/docker/testutil/registry"
    24  	"github.com/docker/docker/testutil/request"
    25  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    26  	"gotest.tools/v3/assert"
    27  	"gotest.tools/v3/assert/cmp"
    28  	is "gotest.tools/v3/assert/cmp"
    29  	"gotest.tools/v3/skip"
    30  )
    31  
    32  func TestPluginInvalidJSON(t *testing.T) {
    33  	defer setupTest(t)()
    34  
    35  	endpoints := []string{"/plugins/foobar/set"}
    36  
    37  	for _, ep := range endpoints {
    38  		t.Run(ep, func(t *testing.T) {
    39  			t.Parallel()
    40  
    41  			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
    42  			assert.NilError(t, err)
    43  			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
    44  
    45  			buf, err := request.ReadBody(body)
    46  			assert.NilError(t, err)
    47  			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))
    48  
    49  			res, body, err = request.Post(ep, request.JSON)
    50  			assert.NilError(t, err)
    51  			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
    52  
    53  			buf, err = request.ReadBody(body)
    54  			assert.NilError(t, err)
    55  			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
    56  		})
    57  	}
    58  }
    59  
    60  func TestPluginInstall(t *testing.T) {
    61  	skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
    62  	skip.If(t, testEnv.OSType == "windows")
    63  	skip.If(t, testEnv.IsRootless, "rootless mode has different view of localhost")
    64  
    65  	ctx := context.Background()
    66  	client := testEnv.APIClient()
    67  
    68  	t.Run("no auth", func(t *testing.T) {
    69  		defer setupTest(t)()
    70  
    71  		reg := registry.NewV2(t)
    72  		defer reg.Close()
    73  
    74  		name := "test-" + strings.ToLower(t.Name())
    75  		repo := path.Join(registry.DefaultURL, name+":latest")
    76  		assert.NilError(t, plugin.CreateInRegistry(ctx, repo, nil))
    77  
    78  		rdr, err := client.PluginInstall(ctx, repo, types.PluginInstallOptions{Disabled: true, RemoteRef: repo})
    79  		assert.NilError(t, err)
    80  		defer rdr.Close()
    81  
    82  		_, err = io.Copy(io.Discard, rdr)
    83  		assert.NilError(t, err)
    84  
    85  		_, _, err = client.PluginInspectWithRaw(ctx, repo)
    86  		assert.NilError(t, err)
    87  	})
    88  
    89  	t.Run("with htpasswd", func(t *testing.T) {
    90  		defer setupTest(t)()
    91  
    92  		reg := registry.NewV2(t, registry.Htpasswd)
    93  		defer reg.Close()
    94  
    95  		name := "test-" + strings.ToLower(t.Name())
    96  		repo := path.Join(registry.DefaultURL, name+":latest")
    97  		auth := &types.AuthConfig{ServerAddress: registry.DefaultURL, Username: "testuser", Password: "testpassword"}
    98  		assert.NilError(t, plugin.CreateInRegistry(ctx, repo, auth))
    99  
   100  		authEncoded, err := json.Marshal(auth)
   101  		assert.NilError(t, err)
   102  
   103  		rdr, err := client.PluginInstall(ctx, repo, types.PluginInstallOptions{
   104  			RegistryAuth: base64.URLEncoding.EncodeToString(authEncoded),
   105  			Disabled:     true,
   106  			RemoteRef:    repo,
   107  		})
   108  		assert.NilError(t, err)
   109  		defer rdr.Close()
   110  
   111  		_, err = io.Copy(io.Discard, rdr)
   112  		assert.NilError(t, err)
   113  
   114  		_, _, err = client.PluginInspectWithRaw(ctx, repo)
   115  		assert.NilError(t, err)
   116  	})
   117  	t.Run("with insecure", func(t *testing.T) {
   118  		skip.If(t, !testEnv.IsLocalDaemon())
   119  
   120  		addrs, err := net.InterfaceAddrs()
   121  		assert.NilError(t, err)
   122  
   123  		var bindTo string
   124  		for _, addr := range addrs {
   125  			ip, ok := addr.(*net.IPNet)
   126  			if !ok {
   127  				continue
   128  			}
   129  			if ip.IP.IsLoopback() || ip.IP.To4() == nil {
   130  				continue
   131  			}
   132  			bindTo = ip.IP.String()
   133  		}
   134  
   135  		if bindTo == "" {
   136  			t.Skip("No suitable interface to bind registry to")
   137  		}
   138  
   139  		regURL := bindTo + ":5000"
   140  
   141  		d := daemon.New(t)
   142  		defer d.Stop(t)
   143  
   144  		d.Start(t, "--insecure-registry="+regURL)
   145  		defer d.Stop(t)
   146  
   147  		reg := registry.NewV2(t, registry.URL(regURL))
   148  		defer reg.Close()
   149  
   150  		name := "test-" + strings.ToLower(t.Name())
   151  		repo := path.Join(regURL, name+":latest")
   152  		assert.NilError(t, plugin.CreateInRegistry(ctx, repo, nil, plugin.WithInsecureRegistry(regURL)))
   153  
   154  		client := d.NewClientT(t)
   155  		rdr, err := client.PluginInstall(ctx, repo, types.PluginInstallOptions{Disabled: true, RemoteRef: repo})
   156  		assert.NilError(t, err)
   157  		defer rdr.Close()
   158  
   159  		_, err = io.Copy(io.Discard, rdr)
   160  		assert.NilError(t, err)
   161  
   162  		_, _, err = client.PluginInspectWithRaw(ctx, repo)
   163  		assert.NilError(t, err)
   164  	})
   165  	// TODO: test insecure registry with https
   166  }
   167  
   168  func TestPluginsWithRuntimes(t *testing.T) {
   169  	skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
   170  	skip.If(t, testEnv.IsRootless, "Test not supported on rootless due to buggy daemon setup in rootless mode due to daemon restart")
   171  	skip.If(t, testEnv.OSType == "windows")
   172  
   173  	dir, err := os.MkdirTemp("", t.Name())
   174  	assert.NilError(t, err)
   175  	defer os.RemoveAll(dir)
   176  
   177  	d := daemon.New(t)
   178  	defer d.Cleanup(t)
   179  
   180  	d.Start(t)
   181  	defer d.Stop(t)
   182  
   183  	ctx := context.Background()
   184  	client := d.NewClientT(t)
   185  
   186  	assert.NilError(t, plugin.Create(ctx, client, "test:latest"))
   187  	defer client.PluginRemove(ctx, "test:latest", types.PluginRemoveOptions{Force: true})
   188  
   189  	assert.NilError(t, client.PluginEnable(ctx, "test:latest", types.PluginEnableOptions{Timeout: 30}))
   190  
   191  	p := filepath.Join(dir, "myrt")
   192  	script := fmt.Sprintf(`#!/bin/sh
   193  	file="%s/success"
   194  	if [ "$1" = "someArg" ]; then
   195  		shift
   196  		file="${file}_someArg"
   197  	fi
   198  
   199  	touch $file
   200  	exec runc $@
   201  	`, dir)
   202  
   203  	assert.NilError(t, os.WriteFile(p, []byte(script), 0777))
   204  
   205  	type config struct {
   206  		Runtimes map[string]types.Runtime `json:"runtimes"`
   207  	}
   208  
   209  	cfg, err := json.Marshal(config{
   210  		Runtimes: map[string]types.Runtime{
   211  			"myrt":     {Path: p},
   212  			"myrtArgs": {Path: p, Args: []string{"someArg"}},
   213  		},
   214  	})
   215  	configPath := filepath.Join(dir, "config.json")
   216  	os.WriteFile(configPath, cfg, 0644)
   217  
   218  	t.Run("No Args", func(t *testing.T) {
   219  		d.Restart(t, "--default-runtime=myrt", "--config-file="+configPath)
   220  		_, err = os.Stat(filepath.Join(dir, "success"))
   221  		assert.NilError(t, err)
   222  	})
   223  
   224  	t.Run("With Args", func(t *testing.T) {
   225  		d.Restart(t, "--default-runtime=myrtArgs", "--config-file="+configPath)
   226  		_, err = os.Stat(filepath.Join(dir, "success_someArg"))
   227  		assert.NilError(t, err)
   228  	})
   229  }
   230  
   231  func TestPluginBackCompatMediaTypes(t *testing.T) {
   232  	skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
   233  	skip.If(t, testEnv.OSType == "windows")
   234  	skip.If(t, testEnv.IsRootless, "Rootless has a different view of localhost (needed for test registry access)")
   235  
   236  	defer setupTest(t)()
   237  
   238  	reg := registry.NewV2(t)
   239  	defer reg.Close()
   240  	reg.WaitReady(t)
   241  
   242  	repo := path.Join(registry.DefaultURL, strings.ToLower(t.Name())+":latest")
   243  
   244  	client := testEnv.APIClient()
   245  
   246  	ctx := context.Background()
   247  	assert.NilError(t, plugin.Create(ctx, client, repo))
   248  
   249  	rdr, err := client.PluginPush(ctx, repo, "")
   250  	assert.NilError(t, err)
   251  	defer rdr.Close()
   252  
   253  	buf := &strings.Builder{}
   254  	assert.NilError(t, jsonmessage.DisplayJSONMessagesStream(rdr, buf, 0, false, nil), buf)
   255  
   256  	// Use custom header here because older versions of the registry do not
   257  	// parse the accept header correctly and does not like the accept header
   258  	// that the default resolver code uses. "Older registries" here would be
   259  	// like the one currently included in the test suite.
   260  	headers := http.Header{}
   261  	headers.Add("Accept", images.MediaTypeDockerSchema2Manifest)
   262  
   263  	resolver := docker.NewResolver(docker.ResolverOptions{
   264  		Headers: headers,
   265  	})
   266  	assert.NilError(t, err)
   267  
   268  	n, desc, err := resolver.Resolve(ctx, repo)
   269  	assert.NilError(t, err, repo)
   270  
   271  	fetcher, err := resolver.Fetcher(ctx, n)
   272  	assert.NilError(t, err)
   273  
   274  	rdr, err = fetcher.Fetch(ctx, desc)
   275  	assert.NilError(t, err)
   276  	defer rdr.Close()
   277  
   278  	var m v1.Manifest
   279  	assert.NilError(t, json.NewDecoder(rdr).Decode(&m))
   280  	assert.Check(t, cmp.Equal(m.MediaType, images.MediaTypeDockerSchema2Manifest))
   281  	assert.Check(t, cmp.Len(m.Layers, 1))
   282  	assert.Check(t, cmp.Equal(m.Layers[0].MediaType, images.MediaTypeDockerSchema2LayerGzip))
   283  }