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