docker.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. package tools
  2. import (
  3. "context"
  4. "io"
  5. "net/netip"
  6. "os"
  7. "strings"
  8. "time"
  9. "github.com/moby/go-archive"
  10. "github.com/moby/moby/api/pkg/stdcopy"
  11. "github.com/moby/moby/api/types/container"
  12. "github.com/moby/moby/api/types/network"
  13. "github.com/moby/term"
  14. "github.com/moby/moby/client"
  15. "github.com/moby/moby/client/pkg/jsonmessage"
  16. v1 "github.com/opencontainers/image-spec/specs-go/v1"
  17. )
  18. func Docker() *docker {
  19. ctx := context.Background()
  20. return &docker{
  21. ctx: ctx,
  22. }
  23. }
  24. type (
  25. docker struct {
  26. ctx context.Context
  27. }
  28. dockerImage struct {
  29. docker *docker
  30. auth string
  31. out io.Writer
  32. }
  33. dockerContainer struct {
  34. docker *docker
  35. id string
  36. name string
  37. config *container.Config
  38. hostconfig *container.HostConfig
  39. network *network.NetworkingConfig
  40. }
  41. )
  42. func (t *docker) client() (*client.Client, error) {
  43. cli, err := client.New(client.FromEnv)
  44. if err != nil {
  45. return nil, err
  46. }
  47. return cli, nil
  48. }
  49. func (t *docker) ContainerList() ([]*dockerContainer, error) {
  50. cli, err := t.client()
  51. if err != nil {
  52. return nil, err
  53. }
  54. defer cli.Close()
  55. containers, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: true})
  56. if err != nil {
  57. return nil, err
  58. }
  59. res := []*dockerContainer{}
  60. for _, ctr := range containers.Items {
  61. c, err := t.ContainerGetByID(ctr.ID)
  62. if err != nil {
  63. return nil, err
  64. }
  65. res = append(res, c)
  66. }
  67. return res, nil
  68. }
  69. func (t *docker) ContainerGetByID(id string) (*dockerContainer, error) {
  70. cli, err := t.client()
  71. if err != nil {
  72. return nil, err
  73. }
  74. defer cli.Close()
  75. c, err := cli.ContainerInspect(context.Background(), id, client.ContainerInspectOptions{})
  76. if err != nil {
  77. return nil, err
  78. }
  79. return &dockerContainer{
  80. docker: t,
  81. id: id,
  82. name: strings.TrimPrefix(c.Container.Name, "/"),
  83. config: c.Container.Config,
  84. hostconfig: c.Container.HostConfig,
  85. network: &network.NetworkingConfig{
  86. EndpointsConfig: map[string]*network.EndpointSettings{},
  87. },
  88. }, nil
  89. }
  90. func (t *docker) ContainerGetByName(name string) (*dockerContainer, error) {
  91. cli, err := t.client()
  92. if err != nil {
  93. return nil, err
  94. }
  95. defer cli.Close()
  96. filters := client.Filters{}
  97. filters.Add("name", name)
  98. containers, err := cli.ContainerList(context.Background(),
  99. client.ContainerListOptions{
  100. Filters: filters,
  101. })
  102. if err != nil {
  103. return nil, err
  104. }
  105. var id string
  106. for _, ctr := range containers.Items {
  107. for _, n := range ctr.Names {
  108. if strings.TrimPrefix(n, "/") == name {
  109. id = ctr.ID
  110. }
  111. }
  112. }
  113. return t.ContainerGetByID(id)
  114. }
  115. func (t *docker) ContainerNew(name string) *dockerContainer {
  116. return &dockerContainer{
  117. name: name,
  118. docker: t,
  119. config: &container.Config{
  120. ExposedPorts: network.PortSet{},
  121. },
  122. hostconfig: &container.HostConfig{
  123. AutoRemove: false,
  124. },
  125. network: &network.NetworkingConfig{
  126. EndpointsConfig: map[string]*network.EndpointSettings{},
  127. },
  128. }
  129. }
  130. func (t *dockerContainer) ID() string { return t.id }
  131. func (t *dockerContainer) Name() string { return t.name }
  132. func (t *dockerContainer) Config() *container.Config { return t.config }
  133. func (t *dockerContainer) HostConfig() *container.HostConfig { return t.hostconfig }
  134. func (t *dockerContainer) NetworkConfig() *network.NetworkingConfig { return t.network }
  135. func (t *dockerContainer) Exec() {}
  136. func (t *dockerContainer) Run(destOut io.Writer, destErr io.Writer, timeout time.Duration) (int64, error) {
  137. err := t.Start()
  138. if err != nil {
  139. return -1, err
  140. }
  141. res, err := t.Wait(timeout)
  142. errs := t.Stop()
  143. if errs != nil {
  144. destErr.Write([]byte(errs.Error()))
  145. }
  146. errs = t.Logs(destOut, destErr)
  147. if errs != nil {
  148. destErr.Write([]byte(errs.Error()))
  149. }
  150. errs = t.Remove()
  151. if errs != nil {
  152. destErr.Write([]byte(errs.Error()))
  153. }
  154. return res, err
  155. }
  156. func (t *dockerContainer) Logs(destOut io.Writer, destErr io.Writer) error {
  157. cli, err := t.docker.client()
  158. if err != nil {
  159. return err
  160. }
  161. defer cli.Close()
  162. res, err := cli.ContainerLogs(t.docker.ctx, t.id, client.ContainerLogsOptions{
  163. ShowStdout: true,
  164. ShowStderr: true,
  165. // Details: true,
  166. })
  167. if err != nil {
  168. return err
  169. }
  170. defer res.Close()
  171. _, err = stdcopy.StdCopy(destOut, destErr, res)
  172. if err != nil {
  173. return err
  174. }
  175. return nil
  176. }
  177. func (t *dockerContainer) AddPort(containerPort, address, hostPort string) error {
  178. cp, err := network.ParsePort(containerPort)
  179. if err != nil {
  180. return err
  181. }
  182. addr, err := netip.ParseAddr(address)
  183. if err != nil {
  184. return err
  185. }
  186. if t.hostconfig.PortBindings == nil {
  187. t.hostconfig.PortBindings = network.PortMap{}
  188. }
  189. t.hostconfig.PortBindings[cp] = []network.PortBinding{
  190. {
  191. HostPort: hostPort,
  192. HostIP: addr,
  193. },
  194. }
  195. return nil
  196. }
  197. func (t *dockerContainer) State() (container.ContainerState, error) {
  198. cli, err := t.docker.client()
  199. if err != nil {
  200. return "", err
  201. }
  202. c, err := cli.ContainerInspect(context.Background(), t.id, client.ContainerInspectOptions{})
  203. if err != nil {
  204. return "", err
  205. }
  206. return c.Container.State.Status, nil
  207. }
  208. func (t *dockerContainer) Wait(timeout time.Duration) (int64, error) {
  209. cli, err := t.docker.client()
  210. if err != nil {
  211. return -1, err
  212. }
  213. defer cli.Close()
  214. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  215. wait := cli.ContainerWait(ctx, t.id, client.ContainerWaitOptions{})
  216. select {
  217. case err := <-wait.Error:
  218. if err != nil {
  219. return -1, err
  220. }
  221. case res := <-wait.Result:
  222. return res.StatusCode, nil
  223. }
  224. return -1, nil
  225. }
  226. func (t *dockerContainer) Create() error {
  227. cli, err := t.docker.client()
  228. if err != nil {
  229. return err
  230. }
  231. defer cli.Close()
  232. create, err := cli.ContainerCreate(context.Background(), client.ContainerCreateOptions{
  233. Name: t.name,
  234. Config: t.config,
  235. HostConfig: t.hostconfig,
  236. NetworkingConfig: t.network,
  237. Platform: &v1.Platform{},
  238. })
  239. if err != nil {
  240. return err
  241. }
  242. t.id = create.ID
  243. return nil
  244. }
  245. func (t *dockerContainer) Start() error {
  246. cli, err := t.docker.client()
  247. if err != nil {
  248. return err
  249. }
  250. defer cli.Close()
  251. _, err = cli.ContainerStart(t.docker.ctx, t.id, client.ContainerStartOptions{})
  252. if err != nil {
  253. return err
  254. }
  255. return nil
  256. }
  257. func (t *dockerContainer) Stop() error {
  258. cli, err := t.docker.client()
  259. if err != nil {
  260. return err
  261. }
  262. defer cli.Close()
  263. _, err = cli.ContainerStop(t.docker.ctx, t.id, client.ContainerStopOptions{})
  264. if err != nil {
  265. return err
  266. }
  267. return nil
  268. }
  269. func (t *dockerContainer) Remove() error {
  270. cli, err := t.docker.client()
  271. if err != nil {
  272. return err
  273. }
  274. defer cli.Close()
  275. _, err = cli.ContainerRemove(t.docker.ctx, t.id,
  276. client.ContainerRemoveOptions{
  277. // RemoveLinks: true,
  278. RemoveVolumes: true,
  279. Force: true,
  280. })
  281. if err != nil {
  282. return err
  283. }
  284. t.id = ""
  285. return nil
  286. }
  287. func (t *docker) Image() *dockerImage {
  288. return &dockerImage{
  289. docker: t,
  290. out: os.Stderr,
  291. }
  292. }
  293. // RegistryAuth is the base64 encoded credentials for the registry
  294. func (t *dockerImage) Auth(RegistryAuth string) { t.auth = RegistryAuth }
  295. func (t *dockerImage) Out(out io.Writer) { t.out = out }
  296. func (t *dockerImage) Build(tag, path, dockerfile string) error {
  297. cli, err := t.docker.client()
  298. if err != nil {
  299. return err
  300. }
  301. defer cli.Close()
  302. tar, err := archive.TarWithOptions(path, &archive.TarOptions{})
  303. if err != nil {
  304. return err
  305. }
  306. opts := client.ImageBuildOptions{
  307. Dockerfile: dockerfile,
  308. Tags: []string{tag},
  309. Remove: true,
  310. }
  311. buildResponse, err := cli.ImageBuild(t.docker.ctx, tar, opts)
  312. if err != nil {
  313. return err
  314. }
  315. defer buildResponse.Body.Close()
  316. if t.out != nil {
  317. fd, isTerminal := term.GetFdInfo(t.out)
  318. return jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, t.out, fd, isTerminal, nil)
  319. }
  320. return nil
  321. }
  322. func (t *dockerImage) Tag(source, target string) error {
  323. cli, err := t.docker.client()
  324. if err != nil {
  325. return err
  326. }
  327. defer cli.Close()
  328. _, err = cli.ImageTag(t.docker.ctx, client.ImageTagOptions{
  329. Source: source,
  330. Target: target,
  331. })
  332. if err != nil {
  333. return err
  334. }
  335. return nil
  336. }
  337. func (t *dockerImage) Push(image string) error {
  338. cli, err := t.docker.client()
  339. if err != nil {
  340. return err
  341. }
  342. defer cli.Close()
  343. res, err := cli.ImagePush(t.docker.ctx, image, client.ImagePushOptions{
  344. RegistryAuth: t.auth,
  345. })
  346. if err != nil {
  347. return err
  348. }
  349. defer res.Close()
  350. if t.out != nil {
  351. fd, isTerminal := term.GetFdInfo(t.out)
  352. return jsonmessage.DisplayJSONMessagesStream(res, t.out, fd, isTerminal, nil)
  353. }
  354. return nil
  355. }
  356. func (t *dockerImage) Remove(image string) error {
  357. cli, err := t.docker.client()
  358. if err != nil {
  359. return err
  360. }
  361. defer cli.Close()
  362. _, err = cli.ImageRemove(t.docker.ctx, image, client.ImageRemoveOptions{})
  363. if err != nil {
  364. return err
  365. }
  366. return nil
  367. }
  368. func (t *dockerImage) Pull(image string, timeout time.Duration) error {
  369. cli, err := t.docker.client()
  370. if err != nil {
  371. return err
  372. }
  373. defer cli.Close()
  374. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  375. res, err := cli.ImagePull(ctx, image, client.ImagePullOptions{
  376. RegistryAuth: t.auth,
  377. })
  378. if err != nil {
  379. return err
  380. }
  381. defer res.Close()
  382. if t.out != nil {
  383. fd, isTerminal := term.GetFdInfo(t.out)
  384. return jsonmessage.DisplayJSONMessagesStream(res, t.out, fd, isTerminal, nil)
  385. } else {
  386. err = res.Wait(ctx)
  387. if err != nil {
  388. return err
  389. }
  390. }
  391. return nil
  392. }