github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/integration-cli/fixtures/plugin/plugin.go (about) 1 package plugin 2 3 import ( 4 "encoding/json" 5 "io" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "time" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/pkg/archive" 14 "github.com/docker/docker/plugin" 15 "github.com/docker/docker/registry" 16 "github.com/pkg/errors" 17 "golang.org/x/net/context" 18 ) 19 20 // CreateOpt is 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 } 31 32 // WithBinary is a CreateOpt to set an custom binary to create the plugin with. 33 // This binary must be statically compiled. 34 func WithBinary(bin string) CreateOpt { 35 return func(cfg *Config) { 36 cfg.binPath = bin 37 } 38 } 39 40 // CreateClient is the interface used for `BuildPlugin` to interact with the 41 // daemon. 42 type CreateClient interface { 43 PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error 44 } 45 46 // Create creates a new plugin with the specified name 47 func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error { 48 tmpDir, err := ioutil.TempDir("", "create-test-plugin") 49 if err != nil { 50 return err 51 } 52 defer os.RemoveAll(tmpDir) 53 54 tar, err := makePluginBundle(tmpDir, opts...) 55 if err != nil { 56 return err 57 } 58 defer tar.Close() 59 60 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 61 defer cancel() 62 63 return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name}) 64 } 65 66 // CreateInRegistry makes a plugin (locally) and pushes it to a registry. 67 // This does not use a dockerd instance to create or push the plugin. 68 // If you just want to create a plugin in some daemon, use `Create`. 69 // 70 // This can be useful when testing plugins on swarm where you don't really want 71 // the plugin to exist on any of the daemons (immediately) and there needs to be 72 // some way to distribute the plugin. 73 func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error { 74 tmpDir, err := ioutil.TempDir("", "create-test-plugin-local") 75 if err != nil { 76 return err 77 } 78 defer os.RemoveAll(tmpDir) 79 80 inPath := filepath.Join(tmpDir, "plugin") 81 if err := os.MkdirAll(inPath, 0755); err != nil { 82 return errors.Wrap(err, "error creating plugin root") 83 } 84 85 tar, err := makePluginBundle(inPath, opts...) 86 if err != nil { 87 return err 88 } 89 defer tar.Close() 90 91 dummyExec := func(m *plugin.Manager) (plugin.Executor, error) { 92 return nil, nil 93 } 94 95 regService, err := registry.NewService(registry.ServiceOptions{V2Only: true}) 96 if err != nil { 97 return err 98 } 99 100 managerConfig := plugin.ManagerConfig{ 101 Store: plugin.NewStore(), 102 RegistryService: regService, 103 Root: filepath.Join(tmpDir, "root"), 104 ExecRoot: "/run/docker", // manager init fails if not set 105 CreateExecutor: dummyExec, 106 LogPluginEvent: func(id, name, action string) {}, // panics when not set 107 } 108 manager, err := plugin.NewManager(managerConfig) 109 if err != nil { 110 return errors.Wrap(err, "error creating plugin manager") 111 } 112 113 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 114 defer cancel() 115 if err := manager.CreateFromContext(ctx, tar, &types.PluginCreateOptions{RepoName: repo}); err != nil { 116 return err 117 } 118 119 if auth == nil { 120 auth = &types.AuthConfig{} 121 } 122 err = manager.Push(ctx, repo, nil, auth, ioutil.Discard) 123 return errors.Wrap(err, "error pushing plugin") 124 } 125 126 func makePluginBundle(inPath string, opts ...CreateOpt) (io.ReadCloser, error) { 127 p := &types.PluginConfig{ 128 Interface: types.PluginConfigInterface{ 129 Socket: "basic.sock", 130 Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}}, 131 }, 132 Entrypoint: []string{"/basic"}, 133 } 134 cfg := &Config{ 135 PluginConfig: p, 136 } 137 for _, o := range opts { 138 o(cfg) 139 } 140 if cfg.binPath == "" { 141 binPath, err := ensureBasicPluginBin() 142 if err != nil { 143 return nil, err 144 } 145 cfg.binPath = binPath 146 } 147 148 configJSON, err := json.Marshal(p) 149 if err != nil { 150 return nil, err 151 } 152 if err := ioutil.WriteFile(filepath.Join(inPath, "config.json"), configJSON, 0644); err != nil { 153 return nil, err 154 } 155 if err := os.MkdirAll(filepath.Join(inPath, "rootfs", filepath.Dir(p.Entrypoint[0])), 0755); err != nil { 156 return nil, errors.Wrap(err, "error creating plugin rootfs dir") 157 } 158 if err := archive.NewDefaultArchiver().CopyFileWithTar(cfg.binPath, filepath.Join(inPath, "rootfs", p.Entrypoint[0])); err != nil { 159 return nil, errors.Wrap(err, "error copying plugin binary to rootfs path") 160 } 161 tar, err := archive.Tar(inPath, archive.Uncompressed) 162 return tar, errors.Wrap(err, "error making plugin archive") 163 } 164 165 func ensureBasicPluginBin() (string, error) { 166 name := "docker-basic-plugin" 167 p, err := exec.LookPath(name) 168 if err == nil { 169 return p, nil 170 } 171 172 goBin, err := exec.LookPath("go") 173 if err != nil { 174 return "", err 175 } 176 installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name) 177 cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("fixtures", "plugin", "basic")) 178 cmd.Env = append(cmd.Env, "CGO_ENABLED=0") 179 if out, err := cmd.CombinedOutput(); err != nil { 180 return "", errors.Wrapf(err, "error building basic plugin bin: %s", string(out)) 181 } 182 return installPath, nil 183 }