github.com/rish1988/moby@v25.0.2+incompatible/testutil/fixtures/plugin/plugin.go (about) 1 package plugin // import "github.com/docker/docker/testutil/fixtures/plugin" 2 3 import ( 4 "context" 5 "encoding/json" 6 "io" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "time" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/events" 14 "github.com/docker/docker/api/types/registry" 15 "github.com/docker/docker/pkg/archive" 16 "github.com/docker/docker/plugin" 17 registrypkg "github.com/docker/docker/registry" 18 "github.com/pkg/errors" 19 ) 20 21 // CreateOpt is passed used to change the default plugin config before 22 // creating it 23 type CreateOpt func(*Config) 24 25 // Config wraps types.PluginConfig to provide some extra state for options 26 // extra customizations on the plugin details, such as using a custom binary to 27 // create the plugin with. 28 type Config struct { 29 *types.PluginConfig 30 binPath string 31 RegistryConfig registrypkg.ServiceOptions 32 } 33 34 // WithInsecureRegistry specifies that the given registry can skip host-key checking as well as fall back to plain http 35 func WithInsecureRegistry(url string) CreateOpt { 36 return func(cfg *Config) { 37 cfg.RegistryConfig.InsecureRegistries = append(cfg.RegistryConfig.InsecureRegistries, url) 38 } 39 } 40 41 // WithBinary is a CreateOpt to set an custom binary to create the plugin with. 42 // This binary must be statically compiled. 43 func WithBinary(bin string) CreateOpt { 44 return func(cfg *Config) { 45 cfg.binPath = bin 46 } 47 } 48 49 // CreateClient is the interface used for `BuildPlugin` to interact with the 50 // daemon. 51 type CreateClient interface { 52 PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error 53 } 54 55 // Create creates a new plugin with the specified name 56 func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error { 57 tmpDir, err := os.MkdirTemp("", "create-test-plugin") 58 if err != nil { 59 return err 60 } 61 defer os.RemoveAll(tmpDir) 62 63 tar, err := makePluginBundle(tmpDir, opts...) 64 if err != nil { 65 return err 66 } 67 defer tar.Close() 68 69 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 70 defer cancel() 71 72 return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name}) 73 } 74 75 // CreateInRegistry makes a plugin (locally) and pushes it to a registry. 76 // This does not use a dockerd instance to create or push the plugin. 77 // If you just want to create a plugin in some daemon, use `Create`. 78 // 79 // This can be useful when testing plugins on swarm where you don't really want 80 // the plugin to exist on any of the daemons (immediately) and there needs to be 81 // some way to distribute the plugin. 82 func CreateInRegistry(ctx context.Context, repo string, auth *registry.AuthConfig, opts ...CreateOpt) error { 83 tmpDir, err := os.MkdirTemp("", "create-test-plugin-local") 84 if err != nil { 85 return err 86 } 87 defer os.RemoveAll(tmpDir) 88 89 inPath := filepath.Join(tmpDir, "plugin") 90 if err := os.MkdirAll(inPath, 0o755); err != nil { 91 return errors.Wrap(err, "error creating plugin root") 92 } 93 94 var cfg Config 95 cfg.PluginConfig = &types.PluginConfig{} 96 for _, o := range opts { 97 o(&cfg) 98 } 99 100 tar, err := makePluginBundle(inPath, opts...) 101 if err != nil { 102 return err 103 } 104 defer tar.Close() 105 106 dummyExec := func(m *plugin.Manager) (plugin.Executor, error) { 107 return nil, nil 108 } 109 110 regService, err := registrypkg.NewService(cfg.RegistryConfig) 111 if err != nil { 112 return err 113 } 114 115 managerConfig := plugin.ManagerConfig{ 116 Store: plugin.NewStore(), 117 RegistryService: regService, 118 Root: filepath.Join(tmpDir, "root"), 119 ExecRoot: "/run/docker", // manager init fails if not set 120 CreateExecutor: dummyExec, 121 LogPluginEvent: func(id, name string, action events.Action) {}, // panics when not set 122 } 123 manager, err := plugin.NewManager(managerConfig) 124 if err != nil { 125 return errors.Wrap(err, "error creating plugin manager") 126 } 127 128 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 129 defer cancel() 130 if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil { 131 return err 132 } 133 134 if auth == nil { 135 auth = ®istry.AuthConfig{} 136 } 137 err = manager.Push(ctx, repo, nil, auth, io.Discard) 138 return errors.Wrap(err, "error pushing plugin") 139 } 140 141 func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) { 142 p := &types.PluginConfig{ 143 Interface: types.PluginConfigInterface{ 144 Socket: "basic.sock", 145 Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}}, 146 }, 147 Entrypoint: []string{"/basic"}, 148 } 149 cfg := &Config{ 150 PluginConfig: p, 151 } 152 for _, o := range opts { 153 o(cfg) 154 } 155 if cfg.binPath == "" { 156 binPath, err := ensureBasicPluginBin() 157 if err != nil { 158 return nil, err 159 } 160 cfg.binPath = binPath 161 } 162 163 configJSON, err := json.Marshal(p) 164 if err != nil { 165 return nil, err 166 } 167 if err := os.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0o644); err != nil { 168 return nil, err 169 } 170 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0o755); err != nil { 171 return nil, errors.Wrap(err, "error creating plugin rootfs dir") 172 } 173 174 // Ensure the mount target paths exist 175 for _, m := range p.Mounts { 176 var stat os.FileInfo 177 if m.Source != nil { 178 stat, err = os.Stat(*m.Source) 179 if err != nil && !os.IsNotExist(err) { 180 return nil, err 181 } 182 } 183 184 if stat == nil || stat.IsDir() { 185 var mode os.FileMode = 0o755 186 if stat != nil { 187 mode = stat.Mode() 188 } 189 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", m.Destination), mode); err != nil { 190 return nil, errors.Wrap(err, "error preparing plugin mount destination path") 191 } 192 } else { 193 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(m.Destination)), 0o755); err != nil { 194 return nil, errors.Wrap(err, "error preparing plugin mount destination dir") 195 } 196 f, err := os.Create(filepath.Join(inPath, "rootfs", m.Destination)) 197 if err != nil && !os.IsExist(err) { 198 return nil, errors.Wrap(err, "error preparing plugin mount destination file") 199 } 200 if f != nil { 201 f.Close() 202 } 203 } 204 } 205 if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil { 206 return nil, errors.Wrap(err, "error copying plugin binary to rootfs path") 207 } 208 tar, err := archive.Tar(inPath, archive.Uncompressed) 209 return tar, errors.Wrap(err, "error making plugin archive") 210 } 211 212 func ensureBasicPluginBin() (string, error) { 213 name := "docker-basic-plugin" 214 p, err := exec.LookPath(name) 215 if err == nil { 216 return p, nil 217 } 218 219 goBin, err := exec.LookPath("go") 220 if err != nil { 221 return "", err 222 } 223 installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name) 224 sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic") 225 cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath) 226 cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GO111MODULE=off") 227 if out, err := cmd.CombinedOutput(); err != nil { 228 return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out)) 229 } 230 return installPath, nil 231 }