github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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/pkg/archive" 14 "github.com/docker/docker/plugin" 15 "github.com/docker/docker/registry" 16 "github.com/pkg/errors" 17 ) 18 19 // CreateOpt is passed used to change the default plugin config before 20 // creating it 21 type CreateOpt func(*Config) 22 23 // Config wraps types.PluginConfig to provide some extra state for options 24 // extra customizations on the plugin details, such as using a custom binary to 25 // create the plugin with. 26 type Config struct { 27 *types.PluginConfig 28 binPath string 29 RegistryConfig registry.ServiceOptions 30 } 31 32 // WithInsecureRegistry specifies that the given registry can skip host-key checking as well as fall back to plain http 33 func WithInsecureRegistry(url string) CreateOpt { 34 return func(cfg *Config) { 35 cfg.RegistryConfig.InsecureRegistries = append(cfg.RegistryConfig.InsecureRegistries, url) 36 } 37 } 38 39 // WithBinary is a CreateOpt to set an custom binary to create the plugin with. 40 // This binary must be statically compiled. 41 func WithBinary(bin string) CreateOpt { 42 return func(cfg *Config) { 43 cfg.binPath = bin 44 } 45 } 46 47 // CreateClient is the interface used for `BuildPlugin` to interact with the 48 // daemon. 49 type CreateClient interface { 50 PluginCreate(context.Context, io.Reader, types.PluginCreateOptions) error 51 } 52 53 // Create creates a new plugin with the specified name 54 func Create(ctx context.Context, c CreateClient, name string, opts ...CreateOpt) error { 55 tmpDir, err := os.MkdirTemp("", "create-test-plugin") 56 if err != nil { 57 return err 58 } 59 defer os.RemoveAll(tmpDir) 60 61 tar, err := makePluginBundle(tmpDir, opts...) 62 if err != nil { 63 return err 64 } 65 defer tar.Close() 66 67 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 68 defer cancel() 69 70 return c.PluginCreate(ctx, tar, types.PluginCreateOptions{RepoName: name}) 71 } 72 73 // CreateInRegistry makes a plugin (locally) and pushes it to a registry. 74 // This does not use a dockerd instance to create or push the plugin. 75 // If you just want to create a plugin in some daemon, use `Create`. 76 // 77 // This can be useful when testing plugins on swarm where you don't really want 78 // the plugin to exist on any of the daemons (immediately) and there needs to be 79 // some way to distribute the plugin. 80 func CreateInRegistry(ctx context.Context, repo string, auth *types.AuthConfig, opts ...CreateOpt) error { 81 tmpDir, err := os.MkdirTemp("", "create-test-plugin-local") 82 if err != nil { 83 return err 84 } 85 defer os.RemoveAll(tmpDir) 86 87 inPath := filepath.Join(tmpDir, "plugin") 88 if err := os.MkdirAll(inPath, 0755); err != nil { 89 return errors.Wrap(err, "error creating plugin root") 90 } 91 92 var cfg Config 93 cfg.PluginConfig = &types.PluginConfig{} 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, io.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 := os.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", "GO111MODULE=off") 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 }