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