github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/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 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "time" 12 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/pkg/archive" 15 "github.com/docker/docker/plugin" 16 "github.com/docker/docker/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 cfg.PluginConfig = &types.PluginConfig{} 95 for _, o := range opts { 96 o(&cfg) 97 } 98 99 tar, err := makePluginBundle(inPath, opts...) 100 if err != nil { 101 return err 102 } 103 defer tar.Close() 104 105 dummyExec := func(m *plugin.Manager) (plugin.Executor, error) { 106 return nil, nil 107 } 108 109 regService, err := registry.NewService(cfg.RegistryConfig) 110 if err != nil { 111 return err 112 } 113 114 managerConfig := plugin.ManagerConfig{ 115 Store: plugin.NewStore(), 116 RegistryService: regService, 117 Root: filepath.Join(tmpDir, "root"), 118 ExecRoot: "/run/docker", // manager init fails if not set 119 CreateExecutor: dummyExec, 120 LogPluginEvent: func(id, name, action string) {}, // panics when not set 121 } 122 manager, err := plugin.NewManager(managerConfig) 123 if err != nil { 124 return errors.Wrap(err, "error creating plugin manager") 125 } 126 127 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 128 defer cancel() 129 if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil { 130 return err 131 } 132 133 if auth == nil { 134 auth = &types.AuthConfig{} 135 } 136 err = manager.Push(ctx, repo, nil, auth, ioutil.Discard) 137 return errors.Wrap(err, "error pushing plugin") 138 } 139 140 func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) { 141 p := &types.PluginConfig{ 142 Interface: types.PluginConfigInterface{ 143 Socket: "basic.sock", 144 Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}}, 145 }, 146 Entrypoint: []string{"/basic"}, 147 } 148 cfg := &Config{ 149 PluginConfig: p, 150 } 151 for _, o := range opts { 152 o(cfg) 153 } 154 if cfg.binPath == "" { 155 binPath, err := ensureBasicPluginBin() 156 if err != nil { 157 return nil, err 158 } 159 cfg.binPath = binPath 160 } 161 162 configJSON, err := json.Marshal(p) 163 if err != nil { 164 return nil, err 165 } 166 if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil { 167 return nil, err 168 } 169 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil { 170 return nil, errors.Wrap(err, "error creating plugin rootfs dir") 171 } 172 173 // Ensure the mount target paths exist 174 for _, m := range p.Mounts { 175 var stat os.FileInfo 176 if m.Source != nil { 177 stat, err = os.Stat(*m.Source) 178 if err != nil && !os.IsNotExist(err) { 179 return nil, err 180 } 181 } 182 183 if stat == nil || stat.IsDir() { 184 var mode os.FileMode = 0755 185 if stat != nil { 186 mode = stat.Mode() 187 } 188 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", m.Destination), mode); err != nil { 189 return nil, errors.Wrap(err, "error preparing plugin mount destination path") 190 } 191 } else { 192 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(m.Destination)), 0755); err != nil { 193 return nil, errors.Wrap(err, "error preparing plugin mount destination dir") 194 } 195 f, err := os.Create(filepath.Join(inPath, "rootfs", m.Destination)) 196 if err != nil && !os.IsExist(err) { 197 return nil, errors.Wrap(err, "error preparing plugin mount destination file") 198 } 199 if f != nil { 200 f.Close() 201 } 202 } 203 } 204 if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil { 205 return nil, errors.Wrap(err, "error copying plugin binary to rootfs path") 206 } 207 tar, err := archive.Tar(inPath, archive.Uncompressed) 208 return tar, errors.Wrap(err, "error making plugin archive") 209 } 210 211 func ensureBasicPluginBin() (string, error) { 212 name := "docker-basic-plugin" 213 p, err := exec.LookPath(name) 214 if err == nil { 215 return p, nil 216 } 217 218 goBin, err := exec.LookPath("go") 219 if err != nil { 220 return "", err 221 } 222 installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name) 223 sourcePath := filepath.Join("github.com", "docker", "docker", "testutil", "fixtures", "plugin", "basic") 224 cmd := exec.Command(goBin, "build", "-o", installPath, sourcePath) 225 cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GO111MODULE=off") 226 if out, err := cmd.CombinedOutput(); err != nil { 227 return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out)) 228 } 229 return installPath, nil 230 }